HashMap底層原始碼實現

2021-10-05 05:27:56 字數 2560 閱讀 6802

首先需要明確的是

hashmap 內部結構:可以看作是陣列和鍊錶結合組成的復合結構,陣列被分為乙個個桶(bucket),每個桶儲存有乙個或多個entry物件,每個entry物件包含三部分:key(鍵)、value(值),next(指向下乙個entry),通過雜湊值決定了entry物件在這個陣列的定址;雜湊值相同的entry物件(鍵值對),則以鍊錶形式儲存。如果鍊錶大小超過樹形轉換的閾值(treeify_threshold= 8),鍊錶就會被改造為樹形(紅黑樹)結構。

問:那hashmap在儲存鍵值對時,是如何確定儲存的位置的?

答:通過hashcode()方法計算鍵的雜湊值==>得到的是乙個整數。

int hash = key.hashcode()
jdk1.8中採用的是位運算。在特定情況下,位運算可以轉換成取模運算(當 b = 2^n 時,a % b = a & (b - 1) )。也是因此,hashmap 才將初始長度設定為 16,且擴容只能是以 2 的倍數(2^n)擴容。原始碼中使用位運算hash & (length - 1) 代替取模運算hash%length,是利用其可以直接對記憶體資料進行操作,不需要轉換成十進位制,來提高運算效率。

static int indexfor(int h, int length)
問:若得到的陣列下標相同怎麼辦?會如何插入?

答:如果該位置已有元素儲存,會插入在鍊錶的頭部。

// 將新進元素指向鍊錶的頭部,再將頭節點指向新元素

問:什麼時候進行擴容?

答:當hashmap中的元素個數超過陣列大小*loadfactor時,就會進行陣列擴容,loadfactor的預設值為0.75,也就是說,預設情況下,陣列大小為16,那麼當hashmap中元素個數超過16*0.75=12的時候,就把陣列的大小擴充套件為2*16=32,即擴大一倍,然後重新計算每個元素在陣列中的位置,而這是乙個非常消耗效能的操作,所以如果我們已經預知hashmap中元素的個數,那麼預設元素的個數能夠有效的提高hashmap的效能。比如說,我們有1000個元素new hashmap(1000), 但是理論上來講new hashmap(1024)更合適,不過上面annegu已經說過,即使是1000,hashmap也自動會將其設定為1024。 但是new hashmap(1024)還不是更合適的,因為0.75*1000 < 1000, 也就是說為了讓0.75 * size > 1000, 我們必須這樣new hashmap(2048)才最合適,既考慮了&的問題,也避免了resize的問題。

問:怎麼進行擴容?(jdk1.8)

答: 圖(a)表示擴容前的key1和key2兩種key確定索引位置的示例,圖(b)表示擴容後key1和key2兩種key確定索引位置的示例,其中hash1是key1對應的雜湊與高位運算結果。

我們在擴充hashmap的時候,不需要像jdk1.7的實現那樣重新計算hash,只需要看看原來的hash值新增的那個bit是1還是0就好了,是0的話索引沒變,是1的話索引變成「原索引+oldcap」,可以看看下圖為16擴充為32的resize示意圖:

問:為什麼hashmap的容量擴容時一定是2的冪次方?

若hashmap容量為15,length-1的二進位制位1110

那麼兩個索引的位置都是14,就會造成分布不均勻了,

增加了碰撞的機率,

減慢了查詢的效率,

造成空間的浪費。

hashmap在1.7和1.8做的比較大的乙個改變

擴容後jdk1.7

1.7之前使用的是陣列加鍊表,它的資料節點是乙個entry節點,就是它的乙個內部類,1.7之前資料插入的過程是使用頭插法,但是hashmap使用頭插法,它在擴容的乙個過程,裡面有乙個resize方法,他又呼叫了乙個transfer的方法,把裡面的一些entry進行了乙個rehash,在這個過程當中,可能會造成乙個鍊錶的環,就可能在下一次get的時候出現乙個死迴圈的情況,其次它也沒有加鎖,在多執行緒併發的情況下,不能保證資料的安全性。

在多個執行緒併發擴容時,會在執行transfer()方法轉移鍵值對時,造成鍊錶成環,導致程式在執行get操作時形成死迴圈

jdk1.8進行了改變,改為陣列+鍊錶+紅黑樹,把原來的乙個entry節點變成了乙個node節點,它整個put過程也做了乙個優化。

參考

HashMap底層實現原理 原始碼

基於雜湊表的 map 介面的實現。此實現提供所有可選的對映操作,並允許使用null值 和 null鍵 檢視構造方法 預設初始的容量為16 arraylist預設初始值是 10 預設載入因子為0.75,什麼意思呢?就是說當資料元素達到容量的75 時,就會進行擴容 方法和map中的方法是一樣的 預設初始...

HashMap底層原始碼解析

目錄 一 分析hashmap的資料結構 1.使用陣列儲存,加快訪問速度 2.陣列中的鍊錶,解決hash衝突 3.使用紅黑樹優化鍊錶,防止大量hash衝突 二 hashmap主要原始碼解讀 三 總結 在看原始碼之前,了解一下它的資料結構和執行過程,才能更快更加有效率的讀懂原始碼。hashmap實際儲存...

HashMap底層原始碼剖析

陣列 單向鍊錶 紅黑樹 陣列 陣列每一項都是乙個鍊錶,其實就是陣列和鍊錶的結合體 單向鍊錶 當法神hash碰撞時,首先會找到陣列對應位置,然後1.8採用尾插入法 1.7採用頭插入法 形成乙個單項鍊表結構 紅黑樹 當陣列中每項的鍊錶長度大於8時,會轉換為紅黑樹 hash碰撞 不同的key可能會產生相同...