HashMap中的hash演算法中的幾個疑問

沉默的背影發表於2019-07-18

HashMap中雜湊演算法的關鍵程式碼

//重新計算雜湊值
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//key如果是null 新hashcode是0 否則 計算新的hashcode
}
//計算陣列槽位
 (n - 1) & hash

HashMap的細節我們不談,只看這個雜湊演算法的細節(h = key.hashCode()) ^ (h >>> 16)

^按位異或運算,只要位不同結果為1,不然結果為0;
>>> 無符號右移:右邊補0

為什麼要無符號右移16位後做異或運算

根據上面的說明我們做一個簡單演練

將h無符號右移16為相當於將高區16位移動到了低區的16位,再與原hashcode做異或運算,可以將高低位二進位制特徵混合起來

從上文可知高區的16位與原hashcode相比沒有發生變化,低區的16位發生了變化

我們可知通過上面(h = key.hashCode()) ^ (h >>> 16)進行運算可以把高區與低區的二進位制特徵混合到低區,那麼為什麼要這麼做呢?

我們都知道重新計算出的新雜湊值在後面將會參與hashmap中陣列槽位的計算,計算公式:(n - 1) & hash,假如這時陣列槽位有16個,則槽位計算如下:

仔細觀察上文不難發現,高區的16位很有可能會被陣列槽位數的二進位制碼鎖遮蔽,如果我們不做剛才移位異或運算,那麼在計算槽位時將丟失高區特徵

也許你可能會說,即使丟失了高區特徵不同hashcode也可以計算出不同的槽位來,但是細想當兩個雜湊碼很接近時,那麼這高區的一點點差異就可能導致一次雜湊碰撞,所以這也是將效能做到極致的一種體現

使用異或運算的原因

 異或運算能更好的保留各部分的特徵,如果採用&運算計算出來的值會向1靠攏,採用|運算計算出來的值會向0靠攏

為什麼槽位數必須使用2^n

1、為了讓雜湊後的結果更加均勻

這個原因我們繼續用上面的例子來說明

假如槽位數不是16,而是17,則槽位計算公式變成:(17 - 1) & hash

從上文可以看出,計算結果將會大大趨同,hashcode參加&運算後被更多位的0遮蔽,計算結果只剩下兩種0和16,這對於hashmap來說是一種災難

2、可以通過位運算e.hash & (newCap - 1)來計算,a % (2^n) 等價於 a & (2^n - 1)  ,位運算的運算效率高於算術運算,原因是算術運算還是會被轉化為位運算

 

 

 

說了這麼多點,上面提到的所有問題,最終目的還是為了讓雜湊後的結果更均勻的分部,減少雜湊碰撞,提升hashmap的執行效率

 

相關文章