深入理解HashMap

redis_logo

HashMap是我們使用非常多的Collection,它是基於哈希表的 Map 接口的實現,以key-value的形式存在。今天我們來深入瞭解一下這個集合的底層原理。


衆所周知,HashMap是一個用於存儲Key-Value鍵值對的集合,每一個鍵值對也叫做Entry。這些個鍵值對(Entry)分散存儲在一個數組當中,這個數組就是HashMap的主幹。

hashmap_logo

這個數組在首次使用時進行初始化,每一個元素的初始值都是Null。
爲了瞭解它,我們從Put方法個Get方法來進行闡述。

一、Put方法的原理

HashMap在調用put方法時會先根據Key值來進行哈希運算來得到結果,即:

index =  Hash(“Key”)

假如計算出的index是2,那麼就會將它放入index爲2的位置,如圖:

hashmap_2

但是再完美的Hash函數也難免會出現index衝突的情況。比如下面這樣。
hashmap_3
這時候我們就可以使用鏈表來解決

HashMap數組的每一個元素不止是一個Entry對象,也是一個鏈表的頭節點。每一個Entry對象通過Next指針指向它的下一個Entry節點。當新來的Entry映射到衝突的數組位置時,只需要插入到對應的鏈表即可:
hashmap_4
需要注意的是,新來的Entry節點插入鏈表時,使用的是“頭插法”。
所以一個完整的HashMap樣子應該是這樣的
hashmap_5

二、Get方法的原理

使用Get方法根據Key來查找Value的時候,發生了什麼呢?

首先會把輸入的Key做一次Hash映射,得到對應的index:

index = Hash(“apple”)

由於剛纔所說的Hash衝突,同一個位置有可能匹配到多個Entry,這時候就需要順着對應鏈表的頭節點,一個一個向下來查找。假設我們要查找的Key是“apple”:
hashmap_6
第一步,我們查看的是頭節點Entry6,Entry6的Key是banana,顯然不是我們要找的結果。

第二步,我們查看的是Next節點Entry1,Entry1的Key是apple,正是我們要找的結果。

之所以把Entry6放在頭節點,是因爲HashMap的發明者認爲,後插入的Entry被查找的可能性更大

三、哈希方法

如何實現一個儘量均勻分佈的Hash函數呢?我們通過利用Key的HashCode值來做某種運算。具體公式如下:

index =  HashCode(Key) &  (Length - 1) 

從公式中我們可以看到運用了HashMap的長度。那麼它的長度是多少呢?答案是16。因爲這個數字可以儘量避免哈希碰撞,減少相同index的機率。如果你不相信的話,換個別的數字試試?

四、HashMap的擴容

HashMap的容量是有限的。當經過多次元素插入,使得HashMap達到一定飽和度時,Key映射位置發生衝突的機率會逐漸提高。

這時候,HashMap需要擴展它的長度,也就是進行Resize。

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

1.Capacity
HashMap的當前長度。上一期曾經說過,HashMap的長度是2的冪。
2.LoadFactor
HashMap負載因子,默認值爲0.75f。

衡量HashMap是否進行Resize的條件如下:

HashMap.Size   >=  Capacity * LoadFactor

1.擴容
創建一個新的Entry空數組,長度是原數組的2倍。

2.ReHash
遍歷原Entry數組,把所有的Entry重新Hash到新數組。爲什麼要重新Hash呢?因爲長度擴大以後,Hash的規則也隨之改變。

讓我們回顧一下Hash公式:
index = HashCode(Key) & (Length - 1)

當原數組長度爲8時,Hash運算是和111B做與運算;新數組長度爲16,Hash運算是和1111B做與運算。Hash結果顯然不同。

參考文章

漫畫:什麼是HashMap?


本文作者: catalinaLi
本文鏈接: http://catalinali.top/2018/knowHashMap/
版權聲明: 原創文章,有問題請評論中留言。非商業轉載請註明作者及出處。

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