JDK7 與 JDK8 中 HashMap 的實現

2021-08-09 07:15:29 字數 3210 閱讀 9536

jdk7中的hashmap

hashmap底層維護乙個陣列,陣列中的每一項都是乙個entry

transient entry table;

我們向 hashmap 中所放置的物件實際上是儲存在該陣列當中;

而map中的key,value則以entry的形式存放在陣列中

static class entryimplements map.entry

通過hash計算出來的值將會使用indexfor方法找到它應該所在的table下標:

static int indexfor(int h, int length)

這個方法其實相當於對table.length取模。

當兩個key通過hashcode計算相同時,則發生了hash衝突(碰撞),hashmap解決hash衝突的方式是用鍊錶。

當發生hash衝突時,則將存放在陣列中的entry設定為新值的next(這裡要注意的是,比如a和b都hash後都對映到下標i中,之前已經有a了,當map.put(b)時,將b放到下標i中,a則為b的next,所以新值存放在陣列中,舊值在新值的鍊錶上)

示意圖:

所以當hash衝突很多時,hashmap退化成煉表。

總結一下map.put後的過程:

當向 hashmap 中 put 一對鍵值時,它會根據 key的 hashcode 值計算出乙個位置, 該位置就是此物件準備往陣列中存放的位置。

如果該位置沒有物件存在,就將此物件直接放進陣列當中;如果該位置已經有物件存在了,則順著此存在的物件的鏈開始尋找(為了判斷是否是否值相同,map不允許鍵值對重複), 如果此鏈上有物件的話,再去使用 equals方法進行比較,如果對此鏈上的每個物件的 equals 方法比較都為 false,則將該物件放到陣列當中,然後將陣列中該位置以前存在的那個物件鏈結到此物件的後面。

值得注意的是,當key為null時,都放到table[0]中

private v putfornullkey(v value) }

modcount++;

addentry(0, null, value, 0);

return null; }

當size大於threshold時,會發生擴容。 threshold等於capacity*load factor

void addentry(int hash, k key, v value, int bucketindex)

createentry(hash, key, value, bucketindex); }

jdk7中resize,只有當 size>=threshold並且 table中的那個槽中已經有entry時,才會發生resize。即有可能雖然size>=threshold,但是必須等到每個槽都至少有乙個entry時,才會擴容。還有注意每次resize都會擴大一倍容量

jdk8中的hashmap

一直到jdk7為止,hashmap的結構都是這麼簡單,基於乙個陣列以及多個鍊錶的實現,hash值衝突的時候,就將對應節點以鍊錶的形式儲存。

這樣子的hashmap效能上就抱有一定疑問,如果說成百上千個節點在hash時發生碰撞,儲存乙個鍊錶中,那麼如果要查詢其中乙個節點,那就不可避免的花費o(n)的查詢時間,這將是多麼大的效能損失。這個問題終於在jdk8中得到了解決。再最壞的情況下,鍊錶查詢的時間複雜度為o(n),而紅黑樹一直是o(logn),這樣會提高hashmap的效率。

jdk7中hashmap採用的是位桶+鍊錶的方式,即我們常說的雜湊鍊錶的方式,而jdk8中採用的是位桶+鍊錶/紅黑樹(有關紅黑樹請檢視紅黑樹)的方式,也是非執行緒安全的。當某個位桶的鍊錶的長度達到某個閥值的時候,這個鍊錶就將轉換成紅黑樹。

jdk8中,當同乙個hash值的節點數不小於8時,將不再以單鏈表的形式儲存了,會被調整成一顆紅黑樹(上圖中null節點沒畫)。這就是jdk7與jdk8中hashmap實現的最大區別。

接下來,我們來看下jdk8中hashmap的原始碼實現。

jdk中entry的名字變成了node,原因是和紅黑樹的實現treenode相關聯。

transient node table;

當衝突節點數不小於8-1時,轉換成紅黑樹。

static final int treeify_threshold = 8;

以put方法在jdk8中有了很大的改變

public v put(k key, v value)

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

boolean evict)

if (e.hash == hash &&

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

break;

p = e; }

} v oldvalue = e.value;

if (!onlyifabsent || oldvalue == null) //true || --

e.value = value;

//3.

afternodeaccess(e);

return oldvalue; }

}++modcount;

//判斷閾值,決定是否擴容

if (++size > threshold)

resize();

//4.

afternodeinsertion(evict);

return null; }

treeifybin()就是將鍊錶轉換成紅黑樹。

之前的indeffor()方法消失 了,直接用(tab.length-1)&hash,所以看到這個,代表的就是陣列的下角標。

static final int hash(object key)

JDK7與JDK8中HashMap的實現的區別

hashmap底層維護乙個陣列,陣列中的每一項都是乙個entry transient entry table 我們向 hashmap 中所放置的物件實際上是儲存在該陣列當中 而map中的key,value則以entry的形式存放在陣列中 static class entryimplements ma...

JDK7 與 JDK8 中 HashMap 的實現

jdk7中的hashmap hashmap底層維護乙個陣列,陣列中的每一項都是乙個entry transient entry table 我們向 hashmap 中所放置的物件實際上是儲存在該陣列當中 而map中的key,value則以entry的形式存放在陣列中 static class entr...

JDK7與JDK8中HashMap的實現比較

hashmap底層維護乙個陣列,陣列中的每一項都是乙個entry transiententry table 我們向 hashmap 中所放置的物件實際上是儲存在該陣列當中 而map中的key,value則以entry的形式存放在陣列中 staticclassentryimplementsmap.en...