關於HashMap的一些思考

1、HashMap的負載因子的做用

當 HashMap 中的元素個數(包含鏈表、紅黑樹上的元素)達到數組長度的0.75倍的時候,開始擴容。
 

2、HashMap的負載因子爲何是0.75

主要是爲了提升空間利用率和減小查詢成本(也能夠說是儘量減小hash衝突)。

3、爲何槽位數必須使用2^n

若是想讓 Hash 結果分佈更加均勻,首先想到的就是使用取餘(%)操做。重點來了:「取餘(%)操做中若是除數是2的冪次則等價於與其除數減一的與(&)操做(也就是說 hash % length == hash & (length - 1) 的前提是 length 是 2 的 n 次方)。」 而且採用二進制位操做 &,相對於 % 可以提升運算效率,這就解釋了 HashMap 的長度爲何是 2 的冪次方。

4、解決Hash衝突的方法

一、開放地址法

公式:fi(key) = (f(key)+di) MOD m (di=0,1,2,3,......,m-1)
key:待放入數組(hash表)的元素;m:數組長度
 
當衝突發生時,使用某種探測技術在散列表中造成一個探測序列。沿此序列逐個單元地查找,直到找到給定的關鍵字,或者碰到一個開放的地址(即該地址單元爲空)爲止(若要插入,在探查到開放的地址,則可將待插入的新結點存人該地址單元)。查找時探測到開放的地址則代表表中無待查的關鍵字,即查找失敗。

(1)線性探測法

思想是:經過公式計算出元素在數組中的下標,若是下標上沒有元素,直接放進去;若是下標中有元素,則公式中的 di 依次 +1 從新計算,直到查找到沒有元素的下標。否則數組就滿了,須要擴容。

(2)二次探測法

思想是:經過改變 di 的計算方式來查詢沒有元素的下標,具體計算方式就是 di=-12,12,-22,22,…,-(q * 10 + 2),(q * 10 + 2),q <=m / 2。至於這個 di 的取值我也沒研究,摘抄過來的,可是這個探測法的思想得知道。
考慮的狀況是,若是經過公式計算出來下標以後的全部下標都有元素佔據了,而這個下標的前面的有空閒的,經過第一種方法能夠算出來,可是計算的次數比較多,經過這個方法能夠減小計算次數。

(3)僞隨機數探測再散列

思想是:di 的值是經過隨機函數獲得的。若是隨機函數的種子相同,那麼得出來的 di 也相同,查詢就ok了。
 
總之,開放定址法只要在散列表未填滿時,老是能找到不發生衝突的地址,是咱們經常使用的解決衝突的辦法。

二、拉鍊法

就是當產生 Hash 衝突時,在衝突的節點上造成鏈表,HashMap 就是使用的拉鍊法解決的 Hash 衝突。

5、爲何鏈表長度達到 8 的時候就要轉爲紅黑樹了?

當使用 0.75 做爲負載因子時,鏈表中的長度達到 8 幾乎是不可能的,均衡策略吧。
引用 HashMap 源碼中的註釋:
* 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
* more: less than 1 in ten million 

6、HashMap擴容時元素的位置發生了什麼變化?

分爲三種狀況:
  • 對於數組上的元素:直接使用已經計算出來的hash值從新計算新下標放入新數組。
  • 對於鏈表:將一條鏈表拆分爲兩條,hash值大於數組長度的新鏈表放在新數組,小於的就放在原數組。
  • 對於紅黑樹:將數拆爲兩條鏈表,hash值大於數組長度的新鏈表放在新數組,小於的就放在原數組,最後,從新判斷兩條鏈表是否須要轉爲紅黑樹。
關鍵代碼:
do {
    next = e.next;
    if ((e.hash & oldCap) == 0) {
        if (loTail == null)
            loHead = e;
        else
            loTail.next = e;
        loTail = e;
    }
    else {
        if (hiTail == null)
            hiHead = e;
        else
            hiTail.next = e;
        hiTail = e;
    }
} while ((e = next) != null);
例如:oldCap 是 16,那麼擴容以後的新數組長度就是 32,鏈表上的元素分別是 7,23,39。(整數的hash值就是自己)
  7 :0000 0111
& 16:0001 0000
---------------
 =  :0000 0000 # 0,仍舊在原位
 
  17:0001 0001
& 16:0001 0000
---------------
 =  :0001 0000 # 非0,須要放在 [17, 32) 之間
 
  23:0001 0111
& 16:0001 0000
---------------
 =  :0001 0000 # 非0,須要放在 [17, 32) 之間
 
  39:0010 0111
& 16:0001 0000
---------------
 =  :0000 0000 # 0,仍舊在原位,由於它的的值大於數組的長度
相關文章
相關標籤/搜索