JDK1.7和1.8 HashMap區別:
1.數組+鏈表改成了數組+鏈表或紅黑樹;
2.表的插入方式從頭插法改成了尾插法,簡單說就是插入時,如果數組位置上已經有元素,1.7將新元素放到數組中,原始節點作為新節點的后繼節點,1.8遍歷鏈表,將元素放置到鏈表的最后;
3.在插入時,1.7先判斷是否需要擴容,再插入,1.8先進行插入,插入完成再判斷是否需要擴容
4.擴容的時候1.7需要對原數組中的元素進行重新hash定位在新數組的位置,1.8采用更簡單的判斷邏輯,位置不變或索引+舊容量大小;
原因:
1.防止發生hash沖突,鏈表長度過長,將時間復雜度由O(n)
降為O(logn)
;
2.因為1.7頭插法擴容時,頭插法會使鏈表發生反轉,多線程環境下會產生環;
A線程在插入節點B,B線程也在插入,遇到容量不夠開始擴容,重新hash,放置元素,采用頭插法,后遍歷到的B節點放入了頭部,這樣形成了環,如下圖所示:
1.7的擴容調用transfer代碼,如下所示:
void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; //A線程如果執行到這一行掛起,B線程開始進行擴容 newTable[i] = e; e = next; } } }
3.擴容的時候為什么1.8 不用重新hash就可以直接定位原節點在新數據的位置呢?
這是由於擴容是擴大為原數組大小的2倍,用於計算數組位置的掩碼僅僅只是高位多了一個1,怎么理解呢?
擴容前長度為16,用於計算(n-1) & hash 的二進制n-1為0000 1111,擴容為32后的二進制就高位多了1,為0001 1111。
因為是& 運算,1和任何數 & 都是它本身,那就分二種情況,如下圖:原數據hashcode高位第4位為0和高位為1的情況;
第四位高位為0,重新hash數值不變,第四位為1,重新hash數值比原來大16(舊數組的容量)
參考博客: