JDK8中HashMap原始碼分析

2021-10-22 22:21:56 字數 3715 閱讀 6665

jdk8對hashmap進行了一些修改,最大的不同就是利用了紅黑樹,所以其由**陣列+(鍊錶或紅黑樹)**組成。

在jdk7及之前的hashmap中,根據key查詢value時,根據hash值我們能夠快速定位到陣列具體下標位置處的鍊錶,但是之後的話,需要順著鍊錶乙個個比較下去才能找到我們需要的,時間複雜度取決於鍊錶的長度,為o(n)。

為了降低這部分的開銷,在jdk8中,當鍊表中的元素超過了8個以後,會將鍊錶轉換為紅黑樹,在這些位置進行查詢的時候可以降低時間複雜度為 o(logn)。

jdk8中hashmap裡面還是乙個陣列,幾個關鍵屬性:

static

final

int default_initial_capacity =16;

// 預設的初始容量是16,必須是2的冪。

static

final

int maximum_capacity =

1<<30;

// 最大容量(必須是2的冪且小於2的30次方,傳入容量過大將被這個值替換)

static

final

float default_load_factor =

0.75f

;// 預設的負載因子

static

final

int treeify_threshold =8;

// 鍊錶轉紅黑樹的閾值

static

final

int untreeify_threshold =6;

// 紅黑樹轉鍊錶的閾值

static

final

int min_treeify_capacity =64;

// 轉紅黑樹的最小的容量大小

如果陣列中儲存的是鍊錶,那麼對應的型別為node:

static

class

node

implements

map.entry

public v put

(k key, v value)

final v putval

(int hash, k key, v value,

boolean onlyifabsent,

boolean evict)

// 如果在該鍊錶中找到了"相等"的 key(== 或 equals)

if(e.hash == hash &&

((k = e.key)

== key ||

(key != null && key.

equals

(k))))

// 此時 break,那麼 e 為鍊錶中[與要插入的新值的 key "相等"]的 node

break

; p = e;}}

// e!=null 說明存在舊值的key與要插入的key"相等"

// 對於我們分析的put操作,下面這個 if 其實就是進行 "值覆蓋",然後返回舊值

if(e != null)

}++modcount;

// 如果 hashmap 由於新插入這個值導致 size 已經超過了閾值,需要進行擴容if(

++size > threshold)

resize()

;afternodeinsertion

(evict)

;return null;

}

和jdk7稍微有點不一樣的地方就是,jdk7是先擴容後插入新值的,jdk8先插值再擴容,不過這個不重要。

final

void

treeifybin

(node

tab,

int hash)

tl = p;

}while

((e = e.next)

!= null);if

((tab[index]

= hd)

!= null)

hd.treeify

(tab);}

}

鍊錶轉紅黑樹要同時滿足兩個條件:

原來單個鍊錶的長度為8,新插入第9個元素。

陣列的長度大於64,陣列長度小於64只會進行擴容,不會轉紅黑樹。

resize() 方法用於初始化陣列或陣列擴容,每次擴容後,容量為原來的 2 倍,並進行資料遷移。

final node

resize()

// 將陣列大小擴大一倍

elseif(

(newcap = oldcap <<1)

< maximum_capacity &&

oldcap >= default_initial_capacity)

// 將閾值擴大一倍

newthr = oldthr <<1;

// double threshold

}else

if(oldthr >0)

// 對應使用 new hashmap(int initialcapacity) 初始化後,第一次 put 的時候

newcap = oldthr;

else

if(newthr ==0)

threshold = newthr;

// 用新的陣列大小初始化新的陣列

node

newtab =

(node

)new

node

[newcap]

; table = newtab;

// 如果是初始化陣列,到這裡就結束了,返回 newtab 即可

if(oldtab != null)

else

}while

((e = next)

!= null);if

(lotail != null)

if(hitail != null)}}

}}return newtab;

}

resize()方法被呼叫的幾種情況:

public v get

(object key)

final node

getnode

(int hash, object key)

while

((e = e.next)

!= null);}

}return null;

}

陣列長度為什麼要是2的冪次方?

在陣列大小是2的n次方情況下,h&length-1相當於h%length,位運算速度更快。

什麼時候擴容?

下面兩種情況會擴容:

怎麼擴容?

擴容為原陣列長度的兩倍。

什麼時候鍊錶轉紅黑樹?

同時滿足兩個條件:

1. 原來單個鍊錶的長度為8,新插入第9個元素。

2. 陣列的長度大於64,陣列長度小於64只會進行擴容,不會轉紅黑樹。

什麼時候紅黑樹轉鍊錶?

鍊錶長度小於6。

hashmap vs hashtable ?

hashtable執行緒安全,所有的方法都加上了synchronized註解,而hashmap執行緒不安全。hashmap會觸發fast-fail,而hashtable不會。

jdk1.8與jdk1.7中hashmap差異?

hashmap原始碼分析jdk8

最近看了下jdk8的hashmap原始碼,相比於7,在儲存結構上有了些改變。1.在jdk8之前,hashmap的儲存結構是陣列 鍊錶的形式,那麼這種方式隨著鍊錶的長度增加,效率也凸顯出來。所以在jdk8中這塊做了優化,當鍊表超過一定長度時轉化為紅黑樹來解決這個問題,下面用流程圖畫出hashmap 的...

JDK8原始碼解析 HashMap(二)

1.hashmap容量大小求值方法 返回2的冪次 static final int tablesizefor int cap 1 為什麼這裡需要 int n cap 1這樣呢?首先我們要明白這個方法的作用是獲取輸入容量大小最近的2的冪次值。假設你傳過來的引數cap是16的話,經過下面的運算得出來的值...

jdk8下HashMap原始碼分析實現原理

1.hashmap 繼承 抽象類abstractmap 實現 介面map cloneable serializable 2.hashmap的初始化 四個建構函式 先看看無參建構函式 構建乙個hashmap例項,並初始化loadfactor default load factor 來看看loadfac...