一、簡介
大家都知道,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 容易導致整型溢出,導致計算精度丟失