默認table數組大小爲16,默認loadFactor=0.75,threshold=數組長度*loadFactor,所以默認爲12。
當集合中元素當size > threshold時進行resize,數組擴充爲原來當2倍。
當table數組當長度大於等於64時,衝突元素的的鏈表元素數量大於等於8時鏈表將被一顆紅黑樹取代。
數組index計算
index=(n - 1) & hash,n爲數組長度,因爲數組的長度總是2的n次方,所以n-1轉換爲二進制低位全部是1,這樣再跟hash與運算,那麼得出的index就更加取決於元素本身而不是數組的長度。
插入邏輯:
獲取hashcode。
計算出hash。
計算table index,index=(n - 1) & hash,n爲table長度。
判斷table[index]是否爲null,如果是直接將此節點放入table[index]。
否則判斷table[index]是否爲TreeNode如果是直接調用方法putTreeVal()將元素插入樹上。
否則連接到鏈表的末尾。如果鏈表上存在元素其key和將要插入的元素的key相等的,則使用新的value替換老的value。
當table數組當長度大於等於64時,衝突元素的的鏈表元素數量大於等於8時鏈表將被一顆紅黑樹取代。當不大於64時進行resize操作。
key的相等比較
如果有元素p滿足:
p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))
則說明key已存在,和數組上元素以及鏈表中對元素的比較都是這種比較規則。
對於鏈表,如果hash相同但是key不相等則直接鏈接到鏈表上。
對於紅黑樹,如果hash相同但是key不相等則按照如下順序比較出大小:
如果實現了Comparable接口則使用compareTo()方法比較大小。
比較字符串表示的類名大小同樣使用compareTo()比較。
調用System.identityHashCode()方法生成hashcode比較大小,如下:
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?-1 : 1),此時一定可以比較出大小
也就是說當衝突元素當hash相等key卻不同時一定要分出個大小。
jdk7中HashMap併發問題。
死鏈問題,擴容時數據丟失,元素被覆蓋。 死鏈問題主要發生在transfer()方法:
void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K, V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K, V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; // 1 newTable[i] = e; // 2 e = next; } while (e != null); } } }併發問題主要出現在1、2兩行代碼,這兩行代碼在併發時會互相覆蓋導致死鏈問題。主要是因爲併發修改了共享變量的next屬性。
例如:數組的index=0處有個鏈表,分別是1和2,1的next是2,兩個線程同時resize時,do while循環時,第一次正常,第二次時,第一個線程已經把兩個關係設置成了2元素的next指向1,此時對於線程2:e指向2元素,next指向1元素。第二個線程,第三次循環,e指向了1元素,此時執行e.next = newTable[i],則爲2元素,那麼就形成了1元素的next指向2元素,2元素的next指向1元素,形成環,線程2就會一直運行下去。
LinkedHashMap
LinkedHashMap遍歷的時候保持了出入的順序。其實現原理爲:LinkedHashMap首先繼承了HashMap,其次其內部Entry繼承了HashMap.Node並多了兩個屬性Entry<K,V> before, after;就是這兩個屬性使LinkedHashMap內所有接單連成了一個雙向鏈表,所以也就有了順序性。