HashMap對hash算法的優化

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        ...
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
        ...

這裏提出一個公式:當n爲2的指數冪,x%n 等同於x&(n - 1)
由於按位與操作比取模的性能要高,所以爲了使用按位與來對hash值取模,HashMap通過tableSizeFor()方法保證了數組長度n爲2的指數冪。

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

HashMap沒有直接使用key的hashCode值,而是拿hashCode值與自身右移16位後的結果做異或操作。
原因是爲了讓key的hashCode的高16位參與取模。
hashCode右移16位後高16位全部爲0:

1111 1111 1111 1111 1111 1111 0101 0101 //右移前
0000 0000 0000 0000 1111 1111 1111 1111 //右移後

而這倆個值的異或操作的結果高16位保持不變,低16位就是原來的低16位與高16位的異或結果。
再來看n-1的值,由於n是2的指數冪,所以不難推出n-1的二進制如下:

0000 0000 0000 0000 0000 0000 0000 0001 //1
0000 0000 0000 0000 0000 0000 0000 0011 //3
0000 0000 0000 0000 0000 0000 0000 0111 //7
0000 0000 0000 0000 0000 0000 0000 1111 //15
0000 0000 0000 0000 0000 0000 0001 1111 //31

顯然,在數據量不多的情況下,n-1的高16位都是0,而與0做按位與結果還是0,相當於將hash值的高16位忽略掉了。
那麼hash值若是存在高16位不同,而低16位完全相同的數時,就會因爲n-1的特性導致對倆個完全不同的hash值取模結果卻相同,產生大量的hash衝突。例如:

1111 0011 1010 1111 0101 0101 0101 0101  //hash值1
1100 1100 1010 1100 0101 0101 0101 0101  //hash值2

因此對hash算法的優化就是爲了讓hashCode的高低位都參與取模,降低衝突概率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章