【Java深刻研究】十一、深刻研究hashmap中的hash算法

1、簡介

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

JDK8中的hash 算法:java

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

取模算法:算法

hash(key)&(length-1)

2、深刻分析

一、取模算法爲何用的是位與運算?

因爲位運算直接對內存數據進行操做,不須要轉成十進制,所以處理速度很是快。數組

2的倍數取模,只要將數與2的倍數-1作按位與運算便可。post

對原理感興趣的能夠參考【Java基礎】1四、位與(&)操做與快速取模優化

二、爲何不直接使用key.hashcode()進行取模運算?

咱們知道hash的目的是爲了儘可能分佈均勻。spa

取模作位與運算的時候,實際上剛剛開始數組的長度通常比較小,只利用了低16位,高16位是用不到的。這種狀況下,產生hash衝突的機率會大大增長。設計

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

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

三、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.一、爲何使用31做爲計算的因子呢?

  • 選擇質數做爲乘子,會大大下降hash衝突的機率。質數的值越大,hash衝突率越低
  • 31參與乘法運算,能夠被 JVM 優化,31 * i = (i << 5) - i
  • 使用 101 計算 hash code 容易致使整型溢出,致使計算精度丟失
相關文章
相關標籤/搜索