HashMap負載因子超過1會發生什麼?

前言

提到HashMap,文章可謂是不可勝數,「詳解HashMap」啊,「HashMap源碼解析」啊,「細說HashMap啊」,「胡說HashMap」啊....數組

再提到內部實現,稍有常識的同窗估計張口就能說出個一二三來,哈希桶啊,鏈表加數組啊,1.8 以後鏈表過長會轉爲紅黑樹啊等等。但今天筆者想要帶來的是,除了探究一下標題上的 HashMap負載因子超過1會發生什麼? 還要看看咱們耳熟能詳的 紅黑樹,它到底在多大數量級會出現? 讓咱們一塊兒測試一下!

回顧

測試以前,先回顧一下 Hashmap 都有哪些重要的元素? 首當其衝重要的就是三個成員變量 capacity、loadFactor、threshold,其中 capacity 和 loadFactor 是能夠從構造函數中傳入的,通俗的來捋一波:bash

capacity : HashMap 中桶的數量, 總容量默認值是 16 。須要注意的是初始容量必須是 2 的冪次方。
關於這個屬性,還讓筆者想起昨天在脈脈上看的小段子,一個員工吐槽:"在老闆的代碼裏發現了new HashMap<> (3)這段代碼"。剛剛筆者也說到初始容量必須是 2 的冪次方,可是寫成 3,也不會報錯,強大的 hashmap 都幫咱們考慮好了,內部用了 tableSizeFor 這個方法,幫咱們轉換了一下:數據結構

static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
intput  3 |  4  |  5  |   8    ......   
output  4 |  4  |  8  |   8
複製代碼

capacity 就是傳入的 3,hashmap 拿到 3 並不會直接使用,通過 tableSizeFor 運算後會獲得 2 的冪次方 4,全部你這裏傳 3 仍是傳 4,hashmap 都會優化爲 4,脈脈上吐槽的員工估計是想嘲笑一下老闆連基礎知識都不紮實吧,我以爲老闆是 Capacity 用不用減一記混了可能比較大吧,也不影響使用。言歸正傳..dom

loadFactor:負載因子,默認 0.75。也就是threshold是根據 capacity * loadfactor 算出來的,HashMap 會根據 threshold 的數值,來決定何時調整空間大小。 這個參數有點意思。首先默認 0.75,這個若是不深究卻是好解釋,取了 0.5~1.0 的中間值嘛,可是若是我不按套路出牌設置爲 2 ,HashMap會怎樣呢?函數

動手看看效果

測試

上代碼:測試

HashMap<String, String> testMap = new HashMap<>(2, 2);
testMap.put("test1", "val");
testMap.put("test2", "val");
testMap.put("test3", "val");
複製代碼

初始 capacity 給了 2,loadFactor 也給了 2,那麼 threshold 即爲 4,容量是 2 ,到 4 才擴容,可是我 put 了三個元素進去容量已經超了,而且尚未觸發擴容,會發生什麼? 將斷點打入 HashMap 的 putVal 方法,觀察執行效果:大數據

  1. 第一個元素 test1 進來,進入了兩個分支:
if ((tab = table)i == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
複製代碼

首先桶是空的,進行初始化的 resize,根據構造函數的參數,初始化的容量只有 2,緊着這進入第二個分支,進行與運算計算出數組的索引,一看 「嗯這地方沒人,我 new 一個放這」。優化

  1. 第二個元素進來,差很少同上,可是由於數組已經不是 null 了,因此沒有進入第一個分支。
  2. 第三個元素進來,由於總長度只有 2,test3 根據 hash 方法計算後的值是 110250867,因此(2 - 1)& 110250867 的結果也只能是 1,key 爲 test3 的元素就被追加到索引爲 1 的元素後方。

也就是說,若是咱們構造函數傳入的 loadFactor 大於 1,HashMap 並不會報錯而是像 hash 衝突同樣,追加到計算出的索引後方,提早造成了鏈表,查找元素的時間複雜度也就高了起來。ui

關於轉換爲紅黑樹

緊接着來看看第二個疑問,到底多大數據量,會出現紅黑樹? 首先 HashMap 有個重要的參數,就是TREEIFY_THRESHOLD 默認是 8,變量名很規範,一眼就能看出是幹什麼的,意如其名就是轉爲樹的閾值,源碼中的首次判斷是否須要轉爲樹,就是直接用的它:this

for (int binCount = 0; ;  ++binCount) {
	if ((e = p.next) == null) {
		p.next = newNode(hash, key, value, null);
		// 判斷是否轉爲紅黑樹
		if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
			treeifyBin(tab, hash);
		break;
	}
	if (e.hash == hash &&
		((k = e.key) == key || (key != null && key.equals(k))))
		break;
	p = e;
}
複製代碼

上面這段代碼也是 putVal 中的一部分,一般負責爲 hash 衝突的節點追加鏈表元素,-1 for 1st這句是源碼裏自帶的,由於 binCount 是從 0 開始嘛,因此閾值減一,解釋了一下。 就像代碼表述的,到了閾值將會調用treeifyBin方法,那麼 treeifyBin 就會直接進行紅黑樹轉換嗎? NO! 咱們看看方法實現的前三行:

final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
複製代碼

方法先校驗了 hash 桶的數量,要是小於MIN_TREEIFY_CAPACITY的話,從新調整大小便可,默認值是 64,也就是說並不僅是有TREEIFY_THRESHOLD一個參數的限制。 知道了從哪裏轉爲紅黑樹,想要進行咱們的測試,最簡單暴力的方式就是將 treeifyBin 方法第二個分支打個斷點😋。 咱們將以前測試的構造函數刪掉,採用默認的構造函數,寫下測試代碼:

HashMap<Integer, String> testMap = new HashMap<>();
int max = 1_000_00000;
for (int i = 0; i < max; i++) {
	testMap.put(RandomUtil.randomInt(Integer.MAX_VALUE), "val");
}
複製代碼

簡單暴力,隨機一千萬個 key,當第一次進入咱們打的斷點時,也就表明第一顆紅黑樹造成。點開 Idea 的 Evaluate 窗口,輸入 this.size 就能看到當前 hashmap 的長度,我記錄了十次第一個紅黑樹造成時的 size大小:

1. 2696228
2. 5685561
3. 559996
4. 2806127
5. 沒出現
6. 6156770
7. 768019
8. 5577574
9. 739868
10. 8870967
複製代碼

十次下來大概平均三百萬數量級左右,會出現第一顆紅黑樹,固然這不是一次很是嚴謹的測試,統計次數,測試數據的隨機分佈性,都有可能影響結果,可是這個結果也能體現兩點:

  1. 紅黑樹沒有想象中那麼容易出現
  2. hashmap 很強大,將數值分佈的很均勻。

結尾

兩點測試就到這裏,咱們能夠思考一下,既然數量級這麼大才會出現紅黑樹,HashMap 還犯得上優化嗎?筆者推測了一下,我的觀點以下:
HashMap 咱們能夠看到類 doc 的@since 註解是1.2,早在 jdk1.2 的時候 HashMap 就出現了,jdk1.2 是什麼時間發佈的呢,搜索可知是 1998 年!約 20 年前的電腦內存等配置和如今已是今非昔比,如今的內存條件,已經能夠操做比當年更大更長的對象了,固然也別忘了 JVM 也要進行調優。

因此,綜合個人觀點來看,HashMap 做爲最經常使用的數據結構之一,進行優化的意義是很是大的!

======================= 原創不易~點個贊再走吧!

相關文章
相關標籤/搜索