談?wù)凥ashMap的hash()方法巧妙之處

筆者個人理解,不正之處,歡迎指正與討論。

先看看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沖突,牛逼!

不正之處,歡迎指正。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容