在分析代碼以前,咱們先拋出下面的問題:
hashmap 擴容時每一個 entry 須要再計算一次 hash 嗎?java
咱們首先看看jdk7中的hashmap的resize實現數組
1 void resize(int newCapacity) { //傳入新的容量 2 Entry[] oldTable = table; //引用擴容前的Entry數組 3 int oldCapacity = oldTable.length; 4 if (oldCapacity == MAXIMUM_CAPACITY) { //擴容前的數組大小若是已經達到最大(2^30)了 5 threshold = Integer.MAX_VALUE; //修改閾值爲int的最大值(2^31-1),這樣之後就不會擴容了 6 return; 7 } 8 9 Entry[] newTable = new Entry[newCapacity]; //初始化一個新的Entry數組 10 transfer(newTable); //!!將數據轉移到新的Entry數組裏 11 table = newTable; //HashMap的table屬性引用新的Entry數組 12 threshold = (int)(newCapacity * loadFactor);//修改閾值 13 }
transfer()方法將原有Entry數組的元素拷貝到新的Entry數組裏優化
1 void transfer(Entry[] newTable) { 2 Entry[] src = table; //src引用了舊的Entry數組 3 int newCapacity = newTable.length; 4 for (int j = 0; j < src.length; j++) { //遍歷舊的Entry數組 5 Entry<K,V> e = src[j]; //取得舊Entry數組的每一個元素 6 if (e != null) { 7 src[j] = null;//釋放舊Entry數組的對象引用(for循環後,舊的Entry數組再也不引用任何對象) 8 do { 9 Entry<K,V> next = e.next; 10 int i = indexFor(e.hash, newCapacity); //!!從新計算每一個元素在數組中的位置 11 e.next = newTable[i]; //標記[1] 12 newTable[i] = e; //將元素放在數組上 13 e = next; //訪問下一個Entry鏈上的元素 14 } while (e != null); 15 } 16 } 17 }
從上面能夠看出在jdk7中,在resize的時候首先閾值是用newCapacity * loadFactor 。而後一個個的遍歷Entry數組,而後看看裏面的元素是否已是一條鏈表了,若是是鏈表的話,那麼就從新計算在新的table中的槽值。this
1 final Node<K,V>[] resize() { 2 Node<K,V>[] oldTab = table; 3 int oldCap = (oldTab == null) ? 0 : oldTab.length; 4 int oldThr = threshold; 5 int newCap, newThr = 0; 6 if (oldCap > 0) { 7 // 超過最大值就再也不擴充了,就只好隨你碰撞去吧 8 if (oldCap >= MAXIMUM_CAPACITY) { 9 threshold = Integer.MAX_VALUE; 10 return oldTab; 11 } 12 // 沒超過最大值,就擴充爲原來的2倍 13 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && 14 oldCap >= DEFAULT_INITIAL_CAPACITY) 15 newThr = oldThr << 1; // double threshold 16 } 17 else if (oldThr > 0) // initial capacity was placed in threshold 18 newCap = oldThr; 19 else { // zero initial threshold signifies using defaults 20 newCap = DEFAULT_INITIAL_CAPACITY; 21 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 22 } 23 // 計算新的resize上限 24 if (newThr == 0) { 25 26 float ft = (float)newCap * loadFactor; 27 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? 28 (int)ft : Integer.MAX_VALUE); 29 } 30 threshold = newThr; 31 @SuppressWarnings({"rawtypes","unchecked"}) 32 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 33 table = newTab; 34 if (oldTab != null) { 35 // 把每一個bucket都移動到新的buckets中 36 for (int j = 0; j < oldCap; ++j) { 37 Node<K,V> e; 38 if ((e = oldTab[j]) != null) { 39 oldTab[j] = null; 40 if (e.next == null) 41 newTab[e.hash & (newCap - 1)] = e; 42 else if (e instanceof TreeNode) 43 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); 44 else { // 鏈表優化重hash的代碼塊 45 Node<K,V> loHead = null, loTail = null; 46 Node<K,V> hiHead = null, hiTail = null; 47 Node<K,V> next; 48 do { 49 next = e.next; 50 // 原索引 51 if ((e.hash & oldCap) == 0) { 52 if (loTail == null) 53 loHead = e; 54 else 55 loTail.next = e; 56 loTail = e; 57 } 58 // 原索引+oldCap 59 else { 60 if (hiTail == null) 61 hiHead = e; 62 else 63 hiTail.next = e; 64 hiTail = e; 65 } 66 } while ((e = next) != null); 67 // 原索引放到bucket裏 68 if (loTail != null) { 69 loTail.next = null; 70 newTab[j] = loHead; 71 } 72 // 原索引+oldCap放到bucket裏 73 if (hiTail != null) { 74 hiTail.next = null; 75 newTab[j + oldCap] = hiHead; 76 } 77 } 78 } 79 } 80 } 81 return newTab; 82 }
經過上面的代碼咱們能夠看到使用的是2次冪的擴展(指長度擴爲原來2倍),因此,元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置。
所以,咱們在擴充HashMap的時候,不須要像JDK1.7的實現那樣從新計算hash,經過使用e.hash & oldCap來計算高位和低位的hash值,來把原來在一個槽位上面的鏈表拆分紅兩個鏈表便可
有一點注意區別,JDK1.7中rehash的時候,舊鏈表遷移新鏈表的時候,若是在新表的數組索引位置相同,則鏈表元素會倒置,可是從上圖能夠看出,JDK1.8不會倒置。code