HashMap原始碼分析 保姆式教程 三

2021-10-14 15:23:15 字數 3239 閱讀 3116

public v get

(object key)

//使用擾動函式使得hash值分布更加均勻

static

final

inthash

(object key)

//根據hash和key查詢node節點

final node

getnode

(int hash, object key)

while

((e = e.next)

!= null)

;//當不存在下乙個節點的時候結束迴圈}}

return null;

//如果hash桶為空或者key對應hash桶的位置上不存在節點,返回null

}

獲取流程:

計算需要查詢的key的hash值

判斷hash桶是不是空以及該hash值對應在hash桶上是否存在節點,不存在直接返回null

對比頭節點的hash值和key是否和需要查詢的key一致,如果一致直接返回頭節點

判斷頭節點在紅黑樹中還是鍊錶中

如果在紅黑樹中,則在紅黑樹中查詢該節點

如果在鍊錶中,則遍歷鍊錶查詢該節點

下面來分析幾個小細節:

1.先比較hash再比較key

我們發現判斷hashmap中節點是否是需要查詢的key的時候使用的判斷條件是

e.hash == hash &&

((k = e.key)

== key ||

(key != null && key.

equals

(k))

)

會先比較一下hash值是否一致再比較key是否相等,這麼做是也是為了查詢效率。

因為通常key都會重寫equals方法,通過equals方法比較的效率明顯是比hash值比較要慢的。

所以再equals判斷之前先比較一次hash值可以提前規避很多不符合的節點,效率上會高一些。

2.先比較頭節點,再遍歷鍊錶(紅黑樹)

我們發現當取到key對應hash桶中的節點的時候,會先比較一下頭節點是否是我們需要查詢的節點,如果不符,再去遍歷鍊錶(紅黑樹)。

因為在hash演算法足夠分散的情況下,很多hash桶的每乙個桶中都只會存在乙個節點,所以先比較第乙個節點再去遍歷也能帶來效率上的提公升。

public v remove

(object key)

//根據hash和key,刪除node節點

final node

removenode

(int hash, object key, object value,

boolean matchvalue,

boolean movable)

p = e;

//p儲存當前遍歷到的節點

}while

((e = e.next)

!= null);}

}//我們要找的節點不為空

if(node != null &&

(!matchvalue ||

(v = node.value)

== value ||

(value != null && value.

equals

(v))))

}return null;

}

為什麼需要負載因子係數?

存在負載因子的原因還在於減輕雜湊衝突,例如,預設情況下初始儲存桶為16,或者通過等待直到完整的16個元素被擴充套件,某些儲存桶中可能有多個元素。因此,載入因子預設為0.75,也就是說,第13個元素的hashmap大小16(閾值= 0.75 * 16 = 12)將擴充套件為32

降低負載因子係數的作用?

在建構函式中,設定較小的負載係數,例如0.5甚至0.25。

如果存在乙個長期存在的map,並且金鑰不是固定的,則可以適當地增加初始大小,減小負載因子,減少衝突的可能性,並減少定址時間。交換時間也值得。

是否在初始化時定義容量?

通過以上源**分析,每次擴充套件都需要重新建立儲存桶陣列,鍊錶,資料轉換等,因此擴充套件成本仍然很高。如果可以準確地設定或估計初始容量(即使較大),則有時值得交換時間。

加入紅黑樹

在jdk1.7中,當鍵的雜湊值相同時,將形成雜湊表。當鍊表上的節點數越來越多時,hashmap的查詢效率降低,查詢效能從o(1)變為o(n)。hashmap中的缺陷。

jdk1.8中的hashmap新增了乙個紅黑樹來彌補這一缺點。當鏈結列表上的節點數超過8個時,hashmap會將鏈結列表轉換為紅黑樹結構。紅黑樹結構的引入將原來降低的查詢效能從o(n)改進為o(lgn)。

為什麼會選擇8作為鍊錶轉紅黑樹的閾值?

在負載因子預設為0.75時,單個hsah槽內元素個數為8的概率小於百萬分之一,所以將7作為乙個分水嶺,當槽內元素等於7時不進行轉換,當元素數量大於等於8時轉換為紅黑樹,槽內元素小於等於6時轉換回鍊錶.

干擾雜湊方法

jdk 1.7中的擾動函式將干擾金鑰四次,但在jdk1.8中僅擾動一次,並且金鑰的hashcode值進行xor運算。

根據前一篇文章的分析,我們知道hashmap使用hash&(length-1)來獲取節點的索引位置,該位置是雜湊值的最後幾位,因此只有低數字在操作中,高階數字被忽略,但是雜湊方法中的xor操作使hashcode的每個位都參與獲取索引位置的操作,因此雜湊函式對映更均勻,並降低了雜湊衝突的可能性。

jdk 1.7在擴容前會判斷hash桶是否為空,如果為空會提前建立;jdk 1.8會在擴容時檢查hash桶是否為空

jdk 1.8在鍊錶轉移的時候引入了低位鍊錶和高位鍊錶進行轉移,效率更高

在jdk 1.7中hashmap使用頭插法在擴容期間執行元素傳輸,在多執行緒情況下,兩個節點有可能形成乙個閉環鍊錶,這將導致在擴容期間造成鍊錶的死迴圈。

但是,在jdk 1.8中,hashmap使用尾插法來執行元素傳輸,每個節點的原始順序不會被顛倒,從而避免出現死迴圈的情況,但1.8中的hashmap仍然是執行緒不安全的,因為在擴容中,hashmap首先指向新的節點陣列,然後在進行元素傳輸,因此在多執行緒情況下,get方法可能獲取不到值。

hashtable是執行緒安全的,它的每個方法中都加入了synchronize方法。當需要多執行緒操作的時候可以使用執行緒安全的concurrenthashmap。concurrenthashmap雖然也是執行緒安全的,但是它的效率比hashtable要高好多倍。因為concurrenthashmap使用了分段鎖,並不對整個資料進行鎖定。

也可使用collections.synchronizedmap();返回乙個執行緒安全的map集合。

HashMap原始碼分析

public hashmap int initialcapacity,float loadfactor 2 接下來是重要的put方法,put方法用於將鍵值對儲存到map中,讓我們來具體分析一下。public v put k key,v value if key null 若key為null,則將va...

HashMap 原始碼分析

1 getentry object key 方法 final entrygetentry object key return null 根據key的hash值計算出索引,得到table中的位置,然後遍歷table處的鍊錶 for entrye table indexfor hash,table.le...

HashMap原始碼分析

public v put k key,v value if key null return putfornullkey value int hash hash key int i indexfor hash,table.length for entrye table i e null e e.nex...