每日一面 - 爲何hashmap默認的負載因子是0.75?背後的統計原理是什麼呢?

1. 爲啥需要 負載因子(defaultLoadFactor)

現在主流的 HashMap,一般的實現思路都是開放地址法+鏈地址法的方式來實現。

即數組 + 鏈表的實現方式,通過計算哈希值,找到數組對應的位置,如果已存在元素,就加到這個位置的鏈表上。在 Java 8 之後,鏈表過長還會轉化爲紅黑樹。紅黑樹相較於原來的鏈表,多佔用了一倍的空間,但是查詢速度快樂一個數量級,屬於空間換時間。 同時,鏈表轉換紅黑樹也是一個耗時的操作。並且,一個效率高的哈希表,這個鏈表不應該過長

所以,如果數組的很多元素上面已經有值了,那麼就需要將這個數組擴充下,重建哈希表,也就是 rehash,因此這個 rehash 相當耗時。那麼什麼時候擴容呢?

**當數組填滿的時候?**那麼在數組快要填滿的時候,會發生很多需要將元素加到對應位置的鏈表上的情況,並且增加產生紅黑樹的概率。這顯然不可取。

這個 defaultLoadFactor 就是一個比較合適的,哈希表需要擴容的時候的 數組中有佔用元素的比例

2. 這個比例如何計算?

其實,這個並沒有一個統一的結論,因爲不同場景下,肯定考慮的方面不同,這個數字最好能最通用。但是,目前不同語言的 defaultLoadFactor 並不一樣,比如 Java 是 0.75,Go 中是 0.65,Dart 中是0.8,python 中是0.762.

這裏參考(https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap/31401836#31401836)的思路,說一種推導方式:

首先,這個基於一個假設,那麼就是當一個事件發生的概率大於 0.5,那麼這件事就是很可能發生的。

然後,假設當前有 s 個桶,那麼每次放入一個元素進入某一個桶的概率就是:

放入了第 n 個元素,那麼,某一個桶元素個數爲e的概率,根據二項式分佈就是:

將e=0代入,得出:

讓這個概率 大於 0.5 也就是 1/2

那麼解這個不等式,得到:

如果讓 s 趨近於無窮大,那麼 n/s 就無限接近於 log(2). 也就是放入的元素數量是所有桶的數量的 log(2) ~ 0.693

3. 爲何 Java 8 中的紅黑樹是鏈表大於8的時候轉換

這個是在 defaultLoadFactor = 0.75 的基礎上,根據泊松分佈概率計算得出的結論。我們要保證,這個界限,不能太大,否則遇到鏈表確實比較長的時候,查詢效率低。更不能太小,否則轉換的性能損耗還有空間佔用大。所以,我們期望,每個桶上面元素個數超過 k 的概率儘量小,並且這個 k 不算很大,就可以了。

首先,對於任意一個 HashMap 的數組上面的元素,存入一個數據,要麼放入要麼不放入,概率分別是 50%,期望 E 是 0.5泊松分佈是二項分佈的極限形式,就是有且只有兩個相互對立的結果的概率分佈,對於這個位置上面鏈表元素個數爲 k ,其概率公式是:

期望 E = 0.5,代入,對於 k=1 到 8 的情況,概率分別是:

0: 0.60653066

1: 0.30326533

2: 0.07581633

3: 0.01263606

4: 0.00157952

5: 0.00015795

6: 0.00001316

7: 0.00000094

8: 0.00000006

k=8 時,概率足夠小,所以採用 8 作爲變成紅黑樹的界限。


本文分享自微信公衆號 - 我的編程喵(MyProCat)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章