HashMap源码分析

来源:互联网 发布:手机登不了淘宝怎么办 编辑:程序博客网 时间:2024/06/09 22:44

这篇文章我们来分析一下HashMap的源码实现,进一步阅读之前强烈建议先浏览一下之前文章《《Java Generics and Collections》笔记-Lists/Maps》中关于Maps的部分。

文件位置:jdk/src/share/classes/java/util/HashMap.java
先看一下它的继承关系
  • java.lang.Object
    • java.util.AbstractMap<K,V>
      • java.util.HashMap<K,V>
可以看到HashMap没有继承Collection,而List、Queue、Set则是继承自Collection的。
 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         }

 835         public final boolean equals(Object o) {
 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             }
可以看到,这里如果key和value都为null,或者key都为null,value相等的话也返回true。
 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 }
现在我们把table的结构图示如下:


接下来我们看一下HashMap的构造方法:

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     }

 272     public HashMap(int initialCapacity) {
 273         this(initialCapacity, DEFAULT_LOAD_FACTOR);
 274     }

 280     public HashMap() {
 281         this(DEFAULT_INITIAL_CAPACITY(16), DEFAULT_LOAD_FACTOR(0.75));
 282     }
上面的构造方法完成了对loadFactorinitialCapacity的赋值操作,但是并没有创建table。

 293     public HashMap(Map<? extends K, ? extends V> m) {
 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     }
该方法是根据已有的Map来创建HashMap,其中函数inflateTable是用来创建table数组的,事实上通过前面的构造函数创建的HashMap也是通过该方法来创建table的,这一点可以从put方法中得到印证:

 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:

 461     final Entry<K,V> getEntry(Object key) {
 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     }
466行计算key对应的hash值,467行中的indexFor方法将得到的hash值对table大小取余得到索引,接下来的for循环就是从链表中查找相应元素。这样contains方法理解起来就没什么问题了。

继续看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                 ;

                        这里就是从table中找到一个元素不为null的位置,保存在index中作为current slot
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();
                 用于fast-fail机制的

 927             Entry<K,V> e = next;
 928             if (e == null)
 929                 throw new NoSuchElementException();
 930
 931             if ((next = e.next) == null) {
 932                 Entry[] t = table;
 933                 while (index < t.length && (next = t[index++]) == null)
 934                     ;
                             这里next如果为null,就继续向下找,更新index
935             }
 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也分析完了。

比较重要的内容已经分析完了,接下来简单看一下其他方法


================================================================================================
 490     public V put(K key, V value) {
 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
 881     void addEntry(int hash, K key, V value, int bucketIndex) {
 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
 593     void transfer(Entry[] newTable, boolean rehash) {
 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     }
回到前面resize:
现在586行只需要让table指向向创建的newTable即可。设置新的阈值threshold
这里可以看出,put方法在不需要调整table的时候复杂度是与相应slot处冲突链的长度成正比的,如果需要扩容,复杂度就变为O(table.length+size),这里复杂度分析需要用到聚合分析。
我们继续看看
 418     public V get(Object key) {
 419         if (key == null)
 420             return getForNullKey();
 421         Entry<K,V> entry = getEntry(key);
 422
 423         return null == entry ? null : entry.getValue();
 424     }

看一下涉及到的两个方法:
getForNullKey
 433     private V getForNullKey() {
 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
 461     final Entry<K,V> getEntry(Object key) {
 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     }
这里也是比较简单的。
 737     public void clear() {
 738         modCount++;
 739         Arrays.fill(table, null);
 740         size = 0;
 741     }
注意一下Arrays.fill的用法,用起来比较方便

 751     public boolean containsValue(Object value) {
 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)。

看一下几个迭代器
 952     private final class ValueIterator extends HashIterator<V> {
 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来获取值。

 999     public Set<K> keySet() {
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的几个结构也是同样的。最后附一张图




1 0
原创粉丝点击