JDK 1.8 中 HashMap 的 hash 算法和尋址算法
HashMap 源碼
hash() 方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
h = key.hashCode() 表示 h 是 key 對象的 hashCode 返回值;
h >>> 16 是 h 右移 16 位,因為 int 是 4 字節,32 位,所以右移 16 位后變成:左邊 16 個 0 + 右邊原 h 的高 16 位;
最后把這兩個進行異或返回。
異或:二進制位運算。如果一樣返回 0,不一樣則返回 1。
例:兩個二進制 110 和 100 進行異或
110
^ 100
結果= 010
putVal() 中尋址部分
tab[i = (n - 1) & hash]
tab 就是 HashMap 里的 table 數組 Node<K,V>[] table ;
n 是這個數組的長度 length;
hash 就是上面 hash() 方法返回的值;
為什么不直接用 hashCode() % length ?
看完源碼會有疑問,為什么不直接用 key 對象的 hashCode 對哈希表長度取模?
尋址為什么不用取模?
對於上面尋址算法,由於計算機對比取模,與運算會更快。所以為了效率,HashMap 中規定了哈希表長度為 2 的 k 次方,而 2^k-1 轉為二進制就是 k 個連續的 1,那么 hash & (k 個連續的 1) 返回的就是 hash 的低 k 個位,該計算結果范圍剛好就是 0 到 2^k-1,即 0 到 length - 1,跟取模結果一樣。
也就是說,哈希表長度 length 為 2 的整次冪時, hash & (length - 1) 的計算結果跟 hash % length 一樣,而且效率還更好。
為什么不直接用 hashCode() 而是用它的高 16 位進行異或計算新 hash 值?
int 類型占 32 位,可以表示 2^32 種數(范圍:-2^31 到 2^31-1),而哈希表長度一般不大,在 HashMap 中哈希表的初始化長度是 16(HashMap 中的 DEFAULT_INITIAL_CAPACITY),如果直接用 hashCode 來尋址,那么相當於只有低 4 位有效,其他高位不會有影響。這樣假如幾個 hashCode 分別是 210、220、2^30,那么尋址結果 index 就會一樣而發生沖突,所以哈希表就不均勻分布了。
為了減少這種沖突,HashMap 中讓 hashCode 的高位也參與了尋址計算(進行擾動),即把 hashCode 高 16 位與 hashCode 進行異或算出 hash,然后根據 hash 來做尋址。
