HashMap數據存儲的過程先根據key獲得hash值,通過 (n - 1) & hash
判斷當前元素存放的位置(這里的 n 指的是數組的長度),如果當前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆蓋,不相同就通過拉鏈法解決沖突。
其中,jdk1.8中擾動函數hash的源碼:
static final int hash(Object key) { int h; // key.hashCode():返回散列值也就是hashcode // ^ :按位異或 // >>>:無符號右移,忽略符號位,空位都以0補齊 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
其中看到在獲得hash值時將key的hashCode異或上其無符號右移16位,Hashmap這么做原因:
防止一些實現比較差的 hashCode() 方法,使用擾動函數之后可以減少碰撞,進一步降低hash沖突的幾率。
打個比方, 當我們的數組長度n為16的時候,哈希碼(字符串“abcabcabcabcabc”的key對應的哈希碼)對(16-1)與操作,對於多個key生成的hashCode,只要哈希碼的后4位為0,不論不論高位怎么變化,最終的結果均為0。 如下圖所示:
1954974080(HashCode) | 111 0100 1000 0110 1000 1001 1000 0000 |
2^4-1=15(length-1) | 000 0000 0000 0000 0000 0000 0000 1111 |
&運算 | 000 0000 0000 0000 0000 0000 0000 0000 |
而加上高16位異或低16位的“擾動函數”后,結果如下:
原HashCode | 1954974080 | 111 0100 1000 0110 1000 1001 1000 0000 |
(>>>16)無符號右移16位 | 29830 | 000 0000 0000 0000 0111 0100 1000 0110 |
^(異或)運算 | 1955003654 | 111 0100 1000 0110 1111 1101 0000 0110 |
2^4-1=15(length-1) | 15 | 000 0000 0000 0000 0000 0000 0000 1111 |
&(與)運算 | 6 | 000 0000 0000 0000 0000 0000 0000 0110 |
可以看到: 擾動函數優化前:1954974080 % 16 = 1954974080 & (16 - 1) = 0 擾動函數優化后:1955003654 % 16 = 1955003654 & (16 - 1) = 6 很顯然,減少了碰撞的幾率。