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中再次優化了這個哈希函數,把 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 侵刪。
經過上邊的分析咱們能夠到以下結論: