深入理解JDK8中的HashMap


   一、首先看一下HashMap的數據結構(數組+鏈表/紅黑樹),如下圖:

 

  1、紅黑樹特性(缺一不可):

(1)、每個節點要么是紅色要么是黑色。

(2)、根節點是黑色。

(3)、所有葉子節點都是黑色(葉子節點為NIL或者NULL節點)。

(4)、不存在兩個連續的紅色節點。

(5)、任意節點(包含跟節點)到其葉子節點的所有路徑都包含相同數目的黑色節點。

  2、為什么HashMap中使用紅黑樹而不使用AVL樹呢?

紅黑樹被稱為弱AVL樹,犧牲了嚴格的高度平衡的優越條件為代價(紅黑樹左右子樹的高度差不超過一倍即可)使其能夠以O(log2 n)的時間復雜度進行搜索、插入、刪除操作;此外,由於它的設計,任何不平衡都會在三次旋轉之內解決。因為HashMap的使用場景中插入和刪除操作是非常頻繁的,所以在HashMap中使用了紅黑樹。

  3、紅黑樹RBT與平衡二叉樹AVL比較:

(1)、紅黑樹和AVL樹類似,都是在進行插入和刪除操作時通過特定操作保持二叉查找樹的平衡,從而獲得較高的查找性能。

(2)、紅黑樹和AVL樹的區別在於它使用顏色來標識節點的高度,它所追求的是局部平衡而不是AVL樹中的非常嚴格的平衡。

(3)、AVL 樹比紅黑樹更加平衡,但AVL樹在插入和刪除的時候也會存在大量的旋轉操作。所以當你的應用涉及到頻繁的插入和刪除操作,切記放棄AVL樹,選擇性能更好的紅黑樹;當然,如果你的應用中涉及的插入和刪除操作並不頻繁,而是查找操作相對更頻繁,那么就優先選擇 AVL 樹進行實現。

   二、HashMap元素插入過程及一些參數的詳解

1、首先,需要了解HashMap源碼中幾個重要的參數:

DEFAULT_INITIAL_CAPACITY:默認初始化大小

MAXIMUM_CAPACITY:最大容量

DEFAULT_LOAD_FACTOR:默認的負載因子

TREEIFY_THRESHOLD:鏈表轉化為紅黑樹的閾值(包含)

UNTREEIFY_THRESHOLD:紅黑樹轉化為鏈表的閾值(包含)

MIN_TREEIFY_CAPACITY:當數組大小小於該值時,不進行鏈表向紅黑樹的轉化,而是進行擴容

2、HashMap存儲元素過程:

 (1)圖中剛開始有計算 key 的 hash 值的設計?

拿到 key 的 hashCode,並將 hashCode 的高16位和 hashCode 進行異或(XOR)運算,得到最終的 hash 值。

 (2)為什么要將 hashCode 的高16位參與運算?

主要是為了在 table 的長度較小的時候,讓高位也參與運算,並且不會有太大的開銷。

(3)為什么鏈表轉紅黑樹的閾值是8?

我們平時在進行方案設計時,必須考慮的兩個很重要的因素是:時間和空間。對於 HashMap 也是同樣的道理,簡單來說,閾值為8是在時間和空間上權衡的結果。紅黑樹節點大小約為鏈表節點的2倍,在節點太少時,紅黑樹的查找性能優勢並不明顯,付出2倍空間的代價不值得。理想情況下,使用隨機的哈希碼,節點分布在 hash 桶中的頻率遵循泊松分布,按照泊松分布的公式計算,鏈表中節點個數為8時的概率為 0.00000006,這個概率足夠低了,並且到8個節點時,紅黑樹的性能優勢也會開始展現出來,因此8是一個較合理的數字。

(4)HashMap 的默認初始容量是多少?HashMap 的容量有什么限制嗎?

默認初始容量是16。HashMap 的容量必須是2的N次方,HashMap 會根據我們傳入的容量計算一個大於等於該容量的最小的2的N次方,例如傳 9,容量為16。

 (5)為什么 HashMap 的容量必須是 2 的 N 次方?

計算索引位置的公式為:(n - 1) & hash,當 n 為 2 的 N 次方時,n - 1 為低位全是 1 的值,此時任何值跟 n - 1 進行 & 運算的結果為該值的低 N 位,達到了和取模同樣的效果,實現了均勻分布。實際上,這個設計就是基於公式:x mod 2^n = x & (2^n - 1),因為 & 運算比 mod 具有更高的效率。當 n 不為 2 的 N 次方時,hash 沖突的概率明顯增大。

(6)為什么HashMap的負載因子默認為0.75?

 在HashMap的類注釋上有如圖一段解釋:大致意思是說負載因子是0.75的時候,空間利用率比較高,而且避免了相當多的Hash沖突,使得底層的鏈表或者是紅黑樹的高度比較低,提升了空間效率。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM