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...