HashMap原理筆記(JDK1.7)

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

HashMap數組每一個元素的初始值都是Null。

HashMap通過put方法把元素插入到HashMap,

HashMap的默認初始化長度爲16(可顯式指定),HashMap的默認負載因子爲0.75(可顯式指定),插入時,當計算出HashMap中的數量達到了16*0.75(12)時,對HashMap的容量進行擴容,擴容爲原來的兩倍,擴容時建立更大的數組,然後移動數據到相應位置。擴容這個過程涉及到 rehash、複製數據等操作,所以非常消耗性能。

插入時,通過一個哈希函數來確定Entry在數組中的插入位置(index),

比如調用 hashMap.put("apple", 0) ,插入一個Key爲“apple"的元素:

index = Hash(“apple”) HashMap採用位運算的哈希函數計算index

假定最後計算出的index是2,那麼結果如下:

因爲HahMap的數組長度是有限的,當插入的Entry越來越多的時候,再完美的哈希函數也會出現哈希衝突的情況,

比如在插入第六個Entry時計算出的index還是2:

HashMap中使用鏈表解決衝突:

HashMap數組的每一個元素不止是一個Entry對象,也是一個鏈表的頭節點。每一個Entry對象通過Next指針指向它的下一個Entry節點。當新來的Entry映射到衝突的數組位置時,只需要插入到對應的鏈表即可:

所以說HashMap的數據結構是通過數組加鏈表實現的。數組和鏈表的存取速度較快,可以加快查詢。

 

HashMap通過get方法通過key來查找value,

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

index = Hash(“apple”)

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

查找時,會先判斷該index位置的值是否爲鏈表,不是鏈表就根據key和key的hashCode來返回值,

爲鏈表則需要遍歷直到 key 及 hashcode 相等時候就返回值。

啥都沒取到就直接返回 null 。

所以實際查找時,找到index等於2的數組位置的值,因爲它是一個鏈表,對它進行遍歷,找到key爲apple

的值返回。

 

在高併發的場景下,兩個線程一起對HashMap擴容,會使鏈表上的引用關係變成:

節點a和b互相引用,形成了一個環,當在數組該位置get尋找對應的key時,就發生了死循環。而且還伴隨有數據丟失的問題。

所以在併發的情況,發生擴容時,可能會產生循環鏈表,在執行get的時候,會觸發死循環,引起CPU的100%問題,所以一定要避免在併發環境下使用HashMap。要併發就使用ConcurrentHashmap。

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