筆者個人理解,不正之處,歡迎指正與討論。
先看看JDK1.8中hash算法的實(shí)現(xiàn),感覺真的很巧妙。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
index = (n - 1) & hash(key) //n表示長度
如果是自己實(shí)現(xiàn)hash算法的話,最簡單的話就是直接用hasCode對取余
index = key.hasCode() % n
在HashMap的實(shí)現(xiàn)中要求n的長度為2的n次冪
對于2的n次冪取余,可以用更加高效的方法
index = key.hasCode() & (n-1)
上面兩種方法都存在一種缺陷,就是取余的計(jì)算結(jié)果對高位是無效的,只是對低位有效,當(dāng)計(jì)算出來的hasCode()只有高位有變化時,取余的結(jié)果還是一樣的。
例如
int hashCode1 = 01110101
int hasdCode2 = 01010101
int index1 = 01110101 & 1111 -> 0101
int index2 - 01010101 & 1111 -> 0101
//十進(jìn)制翻譯
int hashCode1 = 117
int hashCode2 = 85
int index1 = 117 % 16 -> 5
int index2 = 85 % 16 -> 5
從上面的例子可以看出來,當(dāng)key計(jì)算出來的hashCode()只有高位變化時,最終算出來的index索引就會引起hash沖突,如果沖突太多的話,HashMap的效率就會非常低下了。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
再來看看這段代碼,對key的hashCode值進(jìn)行再一次計(jì)算。在java中,hashCode是32位的。
首先,對hashCode進(jìn)行16位的無符號右移。
(我們的例子就假設(shè)hashCode是8位的)
int hashCode1 = 01110101 >>> 4
--> hashCode1 = 00000111
int hasCode2 = 01010101 >>> 4
-->hasCode2 = 00000101
然后,對自身進(jìn)行與或運(yùn)算。
hashCode1 = 01110101 ^ 00000111
--> hashCode1 = 01110010
hashCode2 = 01010101 ^ 00000101
--> hashCode2 = 01010000
最后,取余
hashCode1 = 01110010 & 1111 = 0010
hashCode2 = 01010000 & 1111 = 0000
通過上面的分析,hash的再次計(jì)算能夠把高位的變化影響到了低位的變化,真的很神奇啊
一句神奇的代碼,大大減少了hash沖突,牛逼!
不正之處,歡迎指正。