HashMap是我們使用非常多的Collection,它是基於哈希表的 Map 接口的實現,以key-value的形式存在。今天我們來深入瞭解一下這個集合的底層原理。
衆所周知,HashMap是一個用於存儲Key-Value鍵值對的集合,每一個鍵值對也叫做Entry。這些個鍵值對(Entry)分散存儲在一個數組當中,這個數組就是HashMap的主幹。
這個數組在首次使用時進行初始化,每一個元素的初始值都是Null。
爲了瞭解它,我們從Put方法個Get方法來進行闡述。
一、Put方法的原理
HashMap在調用put方法時會先根據Key值來進行哈希運算來得到結果,即:
index = Hash(“Key”)
假如計算出的index是2,那麼就會將它放入index爲2的位置,如圖:
但是再完美的Hash函數也難免會出現index衝突的情況。比如下面這樣。
這時候我們就可以使用鏈表來解決
HashMap數組的每一個元素不止是一個Entry對象,也是一個鏈表的頭節點。每一個Entry對象通過Next指針指向它的下一個Entry節點。當新來的Entry映射到衝突的數組位置時,只需要插入到對應的鏈表即可:
需要注意的是,新來的Entry節點插入鏈表時,使用的是“頭插法”。
所以一個完整的HashMap樣子應該是這樣的
二、Get方法的原理
使用Get方法根據Key來查找Value的時候,發生了什麼呢?
首先會把輸入的Key做一次Hash映射,得到對應的index:
index = Hash(“apple”)
由於剛纔所說的Hash衝突,同一個位置有可能匹配到多個Entry,這時候就需要順着對應鏈表的頭節點,一個一個向下來查找。假設我們要查找的Key是“apple”:
第一步,我們查看的是頭節點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結果顯然不同。
參考文章
本文作者: catalinaLi
本文鏈接: http://catalinali.top/2018/knowHashMap/
版權聲明: 原創文章,有問題請評論中留言。非商業轉載請註明作者及出處。