HashMap的長度爲何要是2的n次方

HashMap爲了存取高效,要儘可能較少碰撞,就是要儘可能把數據分配均勻,每一個鏈表長度大體相同,這個實現就在把數據存到哪一個鏈表中的算法;
這個算法實際就是取模,hash%length,計算機中直接求餘效率不如位移運算,源碼中作了優化hash&(length-1),
hash%length==hash&(length-1)的前提是length是2的n次方;
爲何這樣能均勻分佈減小碰撞呢?2的n次方實際就是1後面n個0,2的n次方-1  實際就是n個1;
例如長度爲9時候,3&(9-1)=0  2&(9-1)=0 ,都在0上,碰撞了;
例如長度爲8時候,3&(8-1)=3  2&(8-1)=2 ,不一樣位置上,不碰撞;html

 

其實就是按位「與」的時候,每一位都能  &1  ,也就是和1111……1111111進行與運算java

0000 0011     3算法

& 0000 1000      8數組

= 0000 0000      0函數

0000 0010     2優化

& 0000 1000    8spa

= 0000 0000      0code

 -------------------------------------------------------------htm

0000 0011     3圖片

& 0000 0111     7

= 0000 0011       3

0000 0010      2

& 0000 0111      7

= 0000 0010      2

固然若是不考慮效率直接求餘便可(就不須要要求長度必須是2的n次方了)

 

JDK1.8 中 hash 函數的實現

JDK1.8中再次優化了這個哈希函數,把 key 的 hashCode 方法返回值右移16位,即丟棄低16位,高16位全爲0 ,而後在於 hashCode 返回值作異或運算,即高 16 位與低 16 位進行異或運算,這麼作能夠在數組 table 的 length 比較小的時候,也能保證考慮到高低Bit都參與到 hash 的計算中,同時不會有太大的開銷,擾動處理次數也從 4次位運算 + 5次異或運算 下降到 1次位運算 + 1次異或運算

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
複製代碼

進過上述的擾動函數只是獲得了合適的 hash 值,可是尚未肯定在 Node[] 數組中的角標,在 JDK1.7中存在一個函數,JDK1.8中雖然沒有可是隻是把這步運算放到了 put 函數中。咱們就看下這個函數實現:

static int indexFor(int h, int length) {
     return h & (length-1);  // 取模運算
}
複製代碼

爲了讓 hash 值可以對應到現有數組中的位置,咱們上篇文章講到一個方法爲 取模運算,即 hash % length,獲得結果做爲角標位置。可是 HashMap 就厲害了,連這一步取模運算的都優化了。咱們須要知道一個計算機對於2進制的運算是要快於10進制的,取模算是10進制的運算了,而位與運算就要更高效一些了。

咱們知道 HashMap 底層數組的長度老是 2^n ,轉爲二進制老是 1000 即1後邊多個0的狀況。此時一個數與 2^n 取模,等價於 一個數與 2^n - 1作位與運算。而 JDK 中就使用h & (length-1) 運算替代了對 length取模。咱們根據圖片來看一個具體的例子:

 

 

圖片來自:https://tech.meituan.com/java-hashmap.html 侵刪。

小結

經過上邊的分析咱們能夠到以下結論:

  • 在存儲元素以前,HashMap 會對 key 的 hashCode 返回值作進一步擾動函數處理,1.7 中擾動函數使用了 4次位運算 + 5次異或運算,1.8 中下降到 1次位運算 + 1次異或運算
  • 擾動處理後的 hash 與 哈希表數組length -1 作位與運算獲得最終元素儲存的哈希桶角標位置。
相關文章
相關標籤/搜索