【Java深入研究】11、深入研究hashmap中的hash算法


一、簡介

大家都知道,HashMap中定位到桶的位置 是根據Key的hash值與數組的長度取模來計算的。

JDK8中的hash 算法:

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

取模算法:

hash(key)&(length-1)

二、深入分析

1、取模算法為什么用的是位與運算?

由於位運算直接對內存數據進行操作,不需要轉成十進制,因此處理速度非常快。

2的倍數取模,只要將數與2的倍數-1做按位與運算即可。

對原理感興趣的可以參考【Java基礎】14、位與(&)操作與快速取模

2、為什么不直接使用key.hashcode()進行取模運算?

我們知道hash的目的是為了盡量分布均勻。

取模做位與運算的時候,實際上剛剛開始數組的長度一般比較小,只利用了低16位,高16位是用不到的。這種情況下,產生hash沖突的概率會大大增加。

這樣設計保證了對象的hashCode的高16位的變化能反應到低16位中,相比較而言減少了hash沖突的情況 。

選用亦或的方式是因為&和|都會使得結果偏向0或者1 ,並不是均勻的概念。

3、String的hashCode()深入分析

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

推導出的公式如下:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

舉個例子推導計算一下:

假設 n=3
i=0 -> h = 31 * 0 + val[0]
i=1 -> h = 31 * (31 * 0 + val[0]) + val[1]
i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2]
       h = 31*31*31*0 + 31*31*val[0] + 31*val[1] + val[2]
       h = 31^(n-1)*val[0] + 31^(n-2)*val[1] + val[2]

3.1、為什么使用31作為計算的因子呢?

  • 選擇質數作為乘子,會大大降低hash沖突的概率。質數的值越大,hash沖突率越低
  • 31參與乘法運算,可以被 JVM 優化,31 * i = (i << 5) - i
  • 使用 101 計算 hash code 容易導致整型溢出,導致計算精度丟失

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM