HashMap源码分析
来源:互联网 发布:手机登不了淘宝怎么办 编辑:程序博客网 时间:2024/06/09 22:44
这篇文章我们来分析一下HashMap的源码实现,进一步阅读之前强烈建议先浏览一下之前文章《《Java Generics and Collections》笔记-Lists/Maps》中关于Maps的部分。
- java.lang.Object
- java.util.AbstractMap<K,V>
- java.util.HashMap<K,V>
149 static final Entry<?,?>[] EMPTY_TABLE = {};
150
151 /**
152 * The table, resized as necessary. Length MUST Always be a power of two.
153 */
154 transient Entry<K,V>[] table = (Entry<K,V>[])EMPTY_TABLE;
这里涉及到强制类型转换的问题,我们首先来看下面的例子:
15 class Base{}
16 class Derived extends Base{
18 }
7 Base b = new Derived();
8 Derived d = (Derived)b;
9
10 Base bs = new Base();
11 Derived d2 = (Derived)bs;
运行时异常: java.lang.ClassCastException: Base cannot be cast to Derived
子类转换为父类是可以直接转换的,因为子类的内存模型里面包含了父类的部分。父类强制转换为子类要分情况,如果父类的实际类型也是父类,那么是不可以的,因为根据内存模型它的实际内存就只有父类的那么大,不能扩展到下面。但是如果实际类型是子类,那么就很容易转为子类了。
对于这里的泛型道理也是一样的,但是要正确判断泛型的父类与子类。比如HashSet<Object>与HashSet<Integer>不满足父子关系,Set<Object>与HashSet<Object>则满足父子类关系。对于数组类型的泛型,我们知道对于普通类型Father[]是Son[]的父类。对于泛型,List<Father>[]就不是List<Son>[]的父类。至于为什么泛型作此规定可以从文章《Java泛型总结》中2.1小节得到答案。
154行把Entry<?,?>[]强制转为Entry<K,V>[],后者是前者的子类,把父类强制转为子类,只有当父类实际类型是子类的时候才允许,这里由于泛型擦出的原因是满足的。
这里的table就是用来保存实际的数据的,它的元素类型为Entry,接下来我们看一下Entry的结构:
805 static class Entry<K,V> implements Map.Entry<K,V> {
806 final K key;
807 V value;
808 Entry<K,V> next;
809 int hash;
810
811 /**
812 * Creates new entry.
813 */
814 Entry(int h, K k, V v, Entry<K,V> n) {
815 value = v;
816 next = n;
817 key = k;
818 hash = h;
819 }
820
821 public final K getKey() {
822 return key;
823 }
824
825 public final V getValue() {
826 return value;
827 }
829 public final V setValue(V newValue) {
830 V oldValue = value;
831 value = newValue;
832 return oldValue;
833 }
836 if (!(o instanceof Map.Entry))
837 return false;
838 Map.Entry e = (Map.Entry)o;
839 Object k1 = getKey();
840 Object k2 = e.getKey();
841 if (k1 == k2 || (k1 != null && k1.equals(k2))) {
842 Object v1 = getValue();
843 Object v2 = e.getValue();
844 if (v1 == v2 || (v1 != null && v1.equals(v2)))
845 return true;
846 }
847 return false;
848 }
850 public final int hashCode() {
851 return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
852 }
853
854 public final String toString() {
855 return getKey() + "=" + getValue();
856 }
872 }
250 public HashMap(int initialCapacity, float loadFactor) {
251 if (initialCapacity < 0)
252 throw new IllegalArgumentException("Illegal initial capacity: " +
253 initialCapacity);
254 if (initialCapacity > MAXIMUM_CAPACITY)
255 initialCapacity = MAXIMUM_CAPACITY;
256 if (loadFactor <= 0 || Float.isNaN(loadFactor))
257 throw new IllegalArgumentException("Illegal load factor: " +
258 loadFactor);
259
260 this.loadFactor = loadFactor;
261 threshold = initialCapacity;
262 init(); //该方法为空实现
263 }
273 this(initialCapacity, DEFAULT_LOAD_FACTOR);
274 }
280 public HashMap() {
281 this(DEFAULT_INITIAL_CAPACITY(16), DEFAULT_LOAD_FACTOR(0.75));
282 }
上面的构造方法完成了对loadFactor和initialCapacity的赋值操作,但是并没有创建table。
294 this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
295 DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
296 inflateTable(threshold);
297
298 putAllForCreate(m);
299 }
490 public V put(K key, V value) {
491 if (table == EMPTY_TABLE) {
492 inflateTable(threshold);
493 }
接下来我们就来看一下该函数:
315 private void inflateTable(int toSize) {
316 // Find a power of 2 >= toSize
317 int capacity = roundUpToPowerOf2(toSize);
318
319 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
320 table = new Entry[capacity];
321 initHashSeedAsNeeded(capacity);
322 }
该方法比较简单,不再多言。
在继续分析其他方法之前,我们先来看一下一个比较重要的内部类EntrySet:
1080 private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
1081 public Iterator<Map.Entry<K,V>> iterator() {
1082 return newEntryIterator();
1083 }
1084 public boolean contains(Object o) {
1085 if (!(o instanceof Map.Entry))
1086 return false;
1087 Map.Entry<K,V> e = (Map.Entry<K,V>) o;
1088 Entry<K,V> candidate = getEntry(e.getKey());
1089 return candidate != null && candidate.equals(e);
1090 }
1091 public boolean remove(Object o) {
1092 return removeMapping(o) != null;
1093 }
1094 public int size() {
1095 return size;
1096 }
1097 public void clear() {
1098 HashMap.this.clear();
1099 }
1100 }
先来看contains方法中涉及到的一个方法getEntry:
462 if (size == 0) {
463 return null;
464 }
465
466 int hash = (key == null) ? 0 : hash(key);
467 for (Entry<K,V> e = table[indexFor(hash, table.length)];
468 e != null;
469 e = e.next) {
470 Object k;
471 if (e.hash == hash &&
472 ((k = e.key) == key || (key != null && key.equals(k))))
473 return e;
474 }
475 return null;
476 }
继续看EntrySet的remove方法中的removeMapping:该方法与上面的getEntry类似,只不过这里在查找到对应元素后删除之,代码不再贴出。
最后我们重点看一下EntrySet的iterator方法,它调用了newEntryIterator():
977 Iterator<Map.Entry<K,V>> newEntryIterator() {
978 return new EntryIterator();
979 }
接下来我们看一下EntryIterator:
964 private final classEntryIterator extendsHashIterator<Map.Entry<K,V>> {
965 public Map.Entry<K,V> next() {
966 return nextEntry();
967 }
968 }
可见EntryIterator继承了HashIterator,那么我们继续来看一下HashIterator:
905 private abstract class HashIterator<E> implements Iterator<E> {
906 Entry<K,V> next; // next entry to return
907 int expectedModCount; // For fast-fail
908 int index; // current slot
909 Entry<K,V> current; // current entry
910
911 HashIterator() {
912 expectedModCount = modCount;
913 if (size > 0) { // advance to first entry
914 Entry[] t = table;
915 while (index < t.length && (next = t[index++]) == null)
916 ;
917 }
918 }
919
920 public final boolean hasNext() {
921 return next != null;
922 }
923
924 final Entry<K,V> nextEntry() {
925 if (modCount != expectedModCount)
926 throw new ConcurrentModificationException();
927 Entry<K,V> e = next;
928 if (e == null)
930
931 if ((next = e.next) == null) {
932 Entry[] t = table;
933 while (index < t.length && (next = t[index++]) == null)
934 ;
936 current = e;
937 return e;
938 }
939
940 public void remove() {
941 if (current == null)
942 throw new IllegalStateException();
943 if (modCount != expectedModCount)
944 throw new ConcurrentModificationException();
945 Object k = current.key;
946 current = null;
947 HashMap.this.removeEntryForKey(k);
948 expectedModCount = modCount; //主要是因为这里重新设置了expectedModCount,避免了异常
949 }
950 }
HashIterator构造方法中的while循环会遍历table找到一个元素不为null的位置,也就是图中索引为1的位置,此时index = 1,next = A,current = null,结束while循环。因为A不为null,所以hasNext()返回true。nextEntry()中用e保存A,next指向图中B,因为B不为null,931行的if不会进入执行,936行让current指向A,此时index仍然是1,next指向B,这显然是合理的。再调用一次nextEntry就会变为current指向B,next指向C,index仍然为1。再次调用nextEntry,情况会与前两次有所不同,这时
931行next变为null,因此会进入if执行while循环,循环过程中index不断增加,直到index = 6为止,此时next指向D,current指向C,index = 6.现在整个过程就一目了然了。
至于remove方法则比较简单,不再分析。需要特别注意的一点,remove我们删除掉的是current所指的元素,或者说next前面一个元素。初始时如上面所分析的,index = 1,next = A,current = null,这时如果直接调用remove会抛出IllegalStateException。因此,为了删除上图中的A,一定要先调用nextEntry函数。
同理,为了删除B需要继续调用nextEntry。
到此HashIterator就分析完了。回到上面的EntryIterator,它的next()直接调用的HashIterator的nextEntry(),我们上面也已经分析过了。
到此EntryIterator也分析完了。
继续回到前面,newEntryIterator()直接返回 EntryIterator对象作为EntrySet的迭代器,那么newEntryIterator()也分析完了。
继续回到前面,EntrySet的iterator()返回的就是EntryIterator对象,EntrySet的其他重要方法我们也已经在前面分析过了,现在EntrySet也分析完了。
比较重要的内容已经分析完了,接下来简单看一下其他方法
491 if (table == EMPTY_TABLE) {
492 inflateTable(threshold);
493 }
494 if (key == null)
495 return putForNullKey(value);
496 int hash = hash(key);
497 int i = indexFor(hash, table.length);
498 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
499 Object k;
500 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
501 V oldValue = e.value;
502 e.value = value;
503 e.recordAccess(this);
504 return oldValue;
505 }
506 }
507 可见如果只是替换的话,不会增加modCount
508 modCount++;
509 addEntry(hash, key, value, i);
510 return null;
511 }
先来看看putForNullKey
516 private V putForNullKey(V value) {
517 for (Entry<K,V> e = table[0]; e != null; e = e.next) {
518 if (e.key == null) {
519 V oldValue = e.value;
520 e.value = value;
521 e.recordAccess(this);
522 return oldValue;
523 }
524 }
因为key为null的话,其hash=0,所以落在table[0]中,但是具体在哪个位置不一定,所以需要遍历。
525 modCount++;
526 addEntry(0, null, value, 0);
527 return null;
528 }
两个都涉及到了addEntry:
882 if ((size >= threshold) && (null != table[bucketIndex])) {
883 resize(2 * table.length);
884 hash = (null != key) ? hash(key) : 0;
885 bucketIndex = indexFor(hash, table.length);
886 }
887
888 createEntry(hash, key, value, bucketIndex);
889 }
可以看到它需要判断是否需要调整表的大小。我们看一下resize方法:
576 void resize(int newCapacity) {
577 Entry[] oldTable = table;
578 int oldCapacity = oldTable.length;
579 if (oldCapacity == MAXIMUM_CAPACITY) {
580 threshold = Integer.MAX_VALUE;
581 return;
582 }
如果之前的容量已经是MAXIMUM_CAPACITY,这里就不调整table大小了,而是把threshold设置为Integer.MAX_VALUE。
584 Entry[] newTable = new Entry[newCapacity];
585 transfer(newTable, initHashSeedAsNeeded(newCapacity));
586 table = newTable;
587 threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
588 }
重新分配一个table,然后看一下transfer:
594 int newCapacity = newTable.length;
595 for (Entry<K,V> e : table) { //遍历每条链
596 while(null != e) {
597 Entry<K,V> next = e.next;
598 if (rehash) {
599 e.hash = null == e.key ? 0 : hash(e.key);
600 }
601 int i = indexFor(e.hash, newCapacity);
602 e.next = newTable[i];
603 newTable[i] = e;
604 e = next;
605 }
606 }
607 }
419 if (key == null)
420 return getForNullKey();
421 Entry<K,V> entry = getEntry(key);
422
423 return null == entry ? null : entry.getValue();
424 }
看一下涉及到的两个方法:
434 if (size == 0) {
435 return null;
436 }
437 for (Entry<K,V> e = table[0]; e != null; e = e.next) {
438 if (e.key == null)
439 return e.value;
440 }
441 return null;
442 }
getEntry:
462 if (size == 0) {
463 return null;
464 }
465
466 int hash = (key == null) ? 0 : hash(key);
467 for (Entry<K,V> e = table[indexFor(hash, table.length)];
468 e != null;
469 e = e.next) {
470 Object k;
471 if (e.hash == hash &&
472 ((k = e.key) == key || (key != null && key.equals(k))))
473 return e;
474 }
475 return null;
476 }
这里也是比较简单的。
738 modCount++;
739 Arrays.fill(table, null);
740 size = 0;
741 }
注意一下Arrays.fill的用法,用起来比较方便
752 if (value == null)
753 return containsNullValue();
755 Entry[] tab = table;
756 for (int i = 0; i < tab.length ; i++)
757 for (Entry e = tab[i] ; e != null ; e = e.next)
758 if (value.equals(e.value))
759 return true;
760 return false;
761 }
763 /**
764 * Special-case code for containsValue with null argument
765 */
766 private boolean containsNullValue() {
767 Entry[] tab = table;
768 for (int i = 0; i < tab.length ; i++)
769 for (Entry e = tab[i] ; e != null ; e = e.next)
770 if (e.value == null)
771 return true;
772 return false;
773 }
可见两个方法的时间复杂度都是比较高的O(table.length+size)。
953 public V next() {
954 return nextEntry().value;
955 }
956 }
957
958 private final class KeyIterator extends HashIterator<K> {
959 public K next() {
960 return nextEntry().getKey();
961 }
962 }
963
都是在HashIterator的基础上实现的。我们平时遍历HashMap时可以使用keySet方法返回对应的key,然后在通过get来获取值。
1000 Set<K> ks = keySet;
1001 return (ks != null ? ks : (keySet = new KeySet()));
1002 }
1003
1004 private final class KeySet extends AbstractSet<K> {
1005 public Iterator<K> iterator() {
1006 return newKeyIterator();
1007 }
1008 public int size() {
1009 return size;
1010 }
1011 public boolean contains(Object o) {
1012 return containsKey(o);
1013 }
1014 public boolean remove(Object o) {
1015 return HashMap.this.removeEntryForKey(o) != null;
1016 }
1017 public void clear() {
1018 HashMap.this.clear();
1019 }
1020 }
可以看到KeySet与EntrySet也是很像的,只不过这里的iterator返回的是key而已,遍历方法是一样的。
总结
首先我们看到HashMap并没有实现线程安全。它的查找,插入,删除效率与相应位置的冲突链的长度是成正比的,一般情况冲突是比较少的,所以它的查找,插入,删除是比较高效的。对于value的查找则比较低效,它是通过遍历所有元素和table slot来实现的。另外遍历的顺序与插入的顺序没有关系,而且由于存在rehash的过程,同一条链上的元素之间的相对位置也有可能改变。没有实现排序。另外一点就是关于它的迭代器实现方法需要注意一下,还有它的entrySet方法,是基于迭代器在HashMap结构的基础上实现的,所以HashMap的改变会反应到entrySet里面。其他基于EntrySet的几个结构也是同样的。最后附一张图
- 源码分析:HashMap
- 源码分析:HashMap
- HashMap源码分析
- HashMap 源码分析
- HashMap源码分析
- HashMap LinkedHashMap源码分析
- HashMap源码分析
- HashMap 源码分析
- HashMap源码分析
- HashMap源码分析
- HashMap源码分析
- Java HashMap 源码分析
- HashMap源码分析
- java HashMap源码分析
- 源码分析HashMap
- HashMap源码分析
- HashMap源码分析
- HashMap源码分析
- 【C#】23. Excel Addin 开发(1)
- 基于Netty的Redis客户端-Nedis
- 七、基本控制结构之循环结构
- Python GUI 06----Radiobutton
- Windows环境下Android Studio v1.0安装教程
- HashMap源码分析
- 13 layers hardwood core dynea brown film faced plywood
- 突如其来的阿里巴巴二次电话面试
- pmfs 物理地址预分配
- windows平台下vlc编译之四:精简vlc
- Mac xampp
- “字号”“磅”“缇”“点”“毫米”“像素”的关系
- Gson解析内部类时报can not access a member of class xxx with modifiers "final"
- 系统键盘在ios7 ios8上不同效果