轉載自 玻璃貓 程式員小灰
————————————
眾所周知,HashMap是一個用於儲存Key-Value索引值對的集合,每一個索引值對也叫做Entry。這些個索引值對(Entry)分散儲存在一個數組當中,這個數組就是HashMap的主幹。
HashMap數組每一個元素的初始值都是Null。
對於HashMap,我們最常使用的是兩個方法:Get 和 Put。
1.Put方法的原理
調用Put方法的時候發生了什麼呢。
比如調用 hashMap.put("apple", 0) ,插入一個Key為“apple"的元素。這時候我們需要利用一個雜湊函數來確定Entry的插入位置(index):
index = Hash(“apple”)
假定最後計算出的index是2,那麼結果如下:
但是,因為HashMap的長度是有限的,當插入的Entry越來越多時,再完美的Hash函數也難免會出現index衝突的情況。比如下面這樣:
這時候該怎麼辦呢。我們可以利用鏈表來解決。
HashMap數組的每一個元素不止是一個Entry對象,也是一個鏈表的前端節點。每一個Entry對象通過Next指標指向它的下一個Entry節點。當新來的Entry映射到衝突的數組位置時,只需要插入到對應的鏈表即可:
需要注意的是,新來的Entry節點插入鏈表時,使用的是“頭插法”。至於為什麼不插入鏈表尾部,後面會有解釋。
2.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被尋找的可能性更大。
————————————
之前說過,從Key映射到HashMap數組的對應位置,會用到一個Hash函數:
index = Hash(“apple”)
如何?一個盡量均勻分布的Hash函數呢。我們通過利用Key的HashCode值來做某種運算。
index = HashCode(Key) % Length ?
如何進行位元運算呢。有如下的公式(Length是HashMap的長度):
index = HashCode(Key) & (Length - 1)
下面我們以值為“book”的Key來示範整個過程:
1.計算book的hashcode,結果為十進位的3029737,二進位的101110001110101110 1001。
2.假定HashMap長度是預設的16,計算Length-1的結果為十進位的15,二進位的1111。
3.把以上兩個結果做與運算,101110001110101110 1001 & 1111 = 1001,十進位是9,所以 index=9。
可以說,Hash演算法最終得到的index結果,完全取決於Key的Hashcode值的最後幾位。
假設HashMap的長度是10,重複剛才的運算步驟:
單獨看這個結果,表面上並沒有問題。我們再來嘗試一個新的HashCode 1011100011101011101011 :
讓我們再換一個HashCode 101110001110101110 1111 試試 :