JDK原始碼 HashMap 之resize

2022-05-30 13:09:13 字數 4146 閱讀 1230

1.hashmap原始碼閱讀目標

了解具體的資料結構(hash及衝突鍊錶、紅黑樹)和重要方法的具體實現(hashcode、equals、put、resize...)

2.重要方法

hashcode 與 equals都是在abstractmap中定義的

hashcode是各元素hash的累加 h += iter.next().hashcode();

equals 1.是否是本身; 2.是否是map例項; 3.size是否相等; 4.比較每個value

重點在於put、resize具體實現步驟:

put:

1.tab為null或length為0 重新resize

2.位置hash(key) & (n-1)的元素為null,則直接賦值

3.既然對應位置的元素不為null,則要看它有什麼型別(單個元素(hash無衝突)或紅黑樹或鍊錶)

單個元素(新的元素如果與這個元素不相等)則要轉為鍊錶,鍊錶則可能轉為紅黑樹(轉化規則 >= 7)

++modcount

4.++size > threshold 則resize()

remove類似(<=6則轉化為鍊錶)

1

final v putval(int hash, k key, v value, boolean

onlyifabsent,

2boolean

evict)

23if (e.hash == hash &&

24 ((k = e.key) == key || (key != null &&key.equals(k))))

25break

;26 p =e;27}

28}29if (e != null) 36}

37 ++modcount;

38if (++size >threshold)

39resize();

40afternodeinsertion(evict);

41return

null

;42 }

treeifybin: 

鍊錶轉為紅黑樹,紅黑樹較為複雜,所以將單獨另起一篇仔細研究學習

keyset/entryset:

1

newkeyset();

2foreach:

3int mc =modcount;

4for (int i = 0; i < tab.length; ++i)

8if (modcount !=mc)

9throw

new concurrentmodificationexception();

view code

resize:(重點在此)

jdk1.7中,resize時,index取得時,全部採用重新hash的方式進行了。jdk1.8對這個進行了改善。

以前要確定index的時候用的是(e.hash & oldcap-1),是取模取餘,而這裡用到的是(e.hash &oldcap),它有兩種結果,乙個是0,乙個是oldcap,

比如oldcap=8,hash是3,11,19,27時,(e.hash & oldcap)的結果是0,8,0,8,這樣3,19組成新的鍊錶,index為3;而11,27組成新的鍊錶,新分配的index為3+8;

jdk1.7中重寫hash是(e.hash & newcap-1),也就是3,11,19,27對16取餘,也是3,11,3,11,和上面的結果一樣,但是index為3的鍊錶是19,3,index為3+8的鍊錶是

27,11,也就是說1.7中經過resize後資料的順序變成了倒敘,而1.8沒有改變順序。

原理:我們使用的是2次冪的擴充套件(指長度擴為原來2倍),所以,元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置。看下圖可以明白這句話的意思,n為table的長度,圖(a)表示擴容前的key1和key2兩種key確定索引位置的示例,圖(b)表示擴容後key1和key2兩種key確定索引位置的示例,其中hash1是key1對應的雜湊與高位運算結果。

元素在重新計算hash之後,因為n變為2倍,那麼n-1的mask範圍在高位多1bit(紅色),因此新的index就會發生這樣的變化:

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

這個設計確實非常的巧妙,既省去了重新計算hash值的時間,而且同時,由於新增的1bit是0還是1可以認為是隨機的,因此resize的過程,均勻的把之前的衝突的節點分散到新的bucket了。這一塊就是jdk1.8新增的優化點。有一點注意區別,jdk1.7中rehash的時候,舊鍊錶遷移新鍊錶的時候,如果在新錶的陣列索引位置相同,則鍊錶元素會倒置,但是從上圖可以看出,jdk1.8不會倒置。

原文:https:

上面的這位博主已經說的十分清楚了,鄙人也就不獻醜了^_^, 詳情可移步原博。

詳細原始碼解析:在這裡引用的是另一博主的(老艮頭--jdk8:hashmap原始碼解析:resize方法)

final node resize()

/** 如果陣列元素個數在正常範圍內,那麼新的陣列容量為老的陣列容量的2倍(左移1位相當於乘以2)

* 如果擴容之後的新容量小於最大容量 並且 老的陣列容量大於等於預設初始化容量(16),那麼新陣列的擴容閥值設定為老閥值的2倍。(老的陣列容量大於16意味著:要麼建構函式指定了乙個大於16的初始化容量值,要麼已經經歷過了至少一次擴容)

*/else

if ((newcap = oldcap << 1) < maximum_capacity &&oldcap >=default_initial_capacity)

newthr = oldthr << 1; //

double threshold}

//ps2

//執行到這個else if 說明老陣列沒有任何元素

//如果老陣列的擴容閥值大於0,那麼設定新陣列的容量為該閥值

//這一步也就意味著構造該map的時候,指定了初始化容量。

else

if (oldthr > 0) //

initial capacity was placed in threshold

newcap =oldthr;

else

//如果擴容閥值為0 (ps2的情況)

if (newthr == 0)

threshold = newthr; //

設定map的擴容閥值為 新的閥值

@suppresswarnings()

//建立新的陣列(對於第一次新增元素,那麼這個陣列就是第乙個陣列;對於存在oldtab的時候,那麼這個陣列就是要需要擴容到的新陣列)

node newtab = (node)new

node[newcap];

table = newtab; //

將該map的table屬性指向到該新陣列

if (oldtab != null)

//如果與運算結果不為0,說明hash值大於老陣列長度(例如hash值為17)

//此時該元素應該放置到新陣列的高位位置上

//例:老陣列長度16,那麼新陣列長度為32,hash為17的應該放置在陣列的第17個位置上,也就是下標為16,那麼下標為16已經屬於高位了,低位是[0-15],高位是[16-31]

else

} while ((e = next) != null

);

if (lotail != null)

if (hitail != null) }}

}}return newtab; //

返回新陣列

}

JDK原始碼之HashMap

部分重要屬性 存放key,value的陣列 transient node table 存放entry的set transient set entryset hashmap的大小 預設16 transient int size 修改次數 transient int modcount 擴擴容閾值capa...

jdk原始碼解析二之HashMap

hashmap的loadfactor為什麼是0.75?主要涉及到泊松分布的概念,猝 乙個bucket空和非空的概率為0.5,通過牛頓二項式等數學計算,得到這個loadfactor的值為log 2 約等於0.693.同回答者所說,可能小於0.75 大於等於log 2 的factor都能提供更好的效能,...

JDK原始碼學習 HashMap

什麼是hash?hash的意思是 雜湊 音譯做 雜湊 輸入乙個任意長度的資料,進過雜湊運算之後,輸出一段固定長度的資料,作為輸入資料的指紋,輸出的結果就是雜湊值。一般來說輸入資料的空間遠遠大於輸出的雜湊值的空間,輸入不同的資料可能會產生相同的雜湊值,所以很難從雜湊值來逆向推出輸入值是什麼。雜湊函式本...