淺談HashMap 的底層原理


本文整理自漫畫:什么是HashMap? -小灰的文章 。已獲得作者授權。


HashMap 是一個用於存儲Key-Value 鍵值對的集合,每一個鍵值對也叫做Entry。這些個Entry 分散存儲在一個數組當中,這個數組就是HashMap 的主干。
HashMap 數組每一個元素的初始值都是Null
這里寫圖片描述

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被查找的可能性更大。

3. HashMap的初始長度

初始長度為16,且每次自動擴容或者手動初始化的時候必須是2的冪。
如何進行位運算呢?有如下的公式(Length是HashMap的長度):
之前說過,從Key映射到HashMap數組的對應位置,會用到一個Hash函數:
index = Hash(“apple”)
如何實現一個盡量均勻分布的Hash函數呢?我們通過利用Key的HashCode值來做某種運算。
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 的size為什么必須是2的冪?。這是因為2的冪用二進制表示時所有位都為1,例如16-1=15 的二進制就是1111B。我們說了Hash算法是為了讓hash 的分布變得均勻。其實我們可以把1111看成四個通道,表示跟1111 做&運算后分布是均勻的。假如默認長度取10,二進制表示為1010,這樣就相當於有兩個通道是關閉的,所以計算出來的索引重復的幾率比較大。

想看原作者的文章可以看看這個公眾號。
這里寫圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM