hashmap底層原理-核心要點

 

1.基本原理?

jdk1.8 : 數組 + 鏈表/紅黑樹

hashmap的初始默認容量:16,如果new Hashmap的時候指定了,也必須是2的冪次方

hash衝突時:先使用鏈表解決衝突--->鏈表的長度到8,則使用紅黑樹

hash算法:先高16位異或低16位再取模運算 why?參考:https://blog.csdn.net/weixin_34288121/article/details/93446872

先高16位異或低16位再取模運算的目的:減少hash衝突

默認擴容閾值/負載因子:0.75f

 

2.如何put?

1).尋找slot

2).根據情況插入

2.1 slot爲null,沒有hash衝突,直接插入

2.2 slot爲單節點:hash衝突,變成鏈表

2.3 slot爲鏈表,hash衝突,在鏈表的尾部追加節點

2.4 slot爲紅黑樹,將節點插入紅黑樹(涉及到紅黑樹的概念、原則、重新構建,左旋右旋等過程....)

3).hashmap容量不夠,達到條件需要先擴容

 

3.hashmap什麼時候擴容?

擴容是懶加載類型的:put的時候確定是否擴容,擴容時候直接生成一個容量爲2倍的數組,然後進行數據遷移

影響發生Resize的因素有兩個:

1)LoadFactor:HashMap負載因子,默認是0.75f。

當 HashMap.size >= Capacity*LoadFactor 時,HashMap可能會進行Resize。

2)當前數據存儲的數量(即size())大小必須大於等於閾值(例如初始值16,負載因子0.75,閾值爲12);

當前加入的數據是否發生了hash衝突。

 

因爲上面這兩個條件,所以存在下面這些情況

(1)就是hashmap在存值的時候(默認大小爲16,負載因子0.75,閾值12),可能達到最後存滿16個值的時候,再存入第17個值纔會發生擴容現象,因爲前16個值,每個值在底層數組中分別佔據一個位置,並沒有發生hash碰撞。

(2)當然也有可能存儲更多值(超多16個值,最多可以存26個值)都還沒有擴容。原理:前11個值全部hash碰撞,存到數組的同一個位置(這時元素個數小於閾值12,不會擴容),後面所有存入的15個值全部分散到數組剩下的15個位置(這時元素個數大於等於閾值,但是每次存入的元素並沒有發生hash碰撞,所以不會擴容),前面11+15=26,所以在存入第27個值的時候才同時滿足上面兩個條件,這時候纔會發生擴容現象。

 

4.如何擴容?

1)創建一個新的Entry空數組,長度是原來的2倍

2)數據遷移:遍歷原Entry數組,把所有的Entry重新Hash到新數組裏。

爲什麼要重新Hash呢?因爲長度擴大以後,Hash的規則也隨之改變了。

讓我們瞭解一下Hash公式:

jdk1.8 : index = HashCode(key) & (Length - 1)

jdk1.7:  index = HashCode(key)% Length

當你經過運算的時候,你就會發現,兩種方式得到的結果是一致的,所以都正確。

注意:jdk1.7版本下,HashMap並不是線性安全的,在併發的情況下可能會形成鏈表環。

static final int hash(Object key) {   //jdk1.8 & jdk1.7
     int h;
     // h = key.hashCode() 爲第一步 取hashCode值
     // h ^ (h >>> 16)  爲第二步 高位參與運算
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

 

5.擴容時如何數據遷移?

1.slot爲null,表示沒有數據,不需要操作

2.slot爲單個節點,表示正常數據,直接拿到新數組相同下標的位置即可

3.slot爲鏈表:拆分高低位鏈表,低位鏈表放到新數據的相同下標位置,高位鏈表放到新數組的 原slot位下標+原數組長度  位置

4.slot爲紅黑樹:與鏈表相似,因爲hashmap中的紅黑樹不只是紅黑樹,同時還維護了一個鏈表,用來數據遷移使用;同樣拆分出高低位鏈表--------不同的是,如果高低位鏈表,長度大於6,則需要重新轉化爲紅黑樹

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章