總結自一下博客,自己留作記錄及總結。
https://blog.csdn.net/mbshqqb/article/details/79799009,特別全面
https://blog.csdn.net/c139352227/article/details/47861815
https://blog.csdn.net/qq_37113604/article/details/81353626
https://zhuanlan.zhihu.com/p/39518031
下面這些是比較小的知識點,其餘知識(如擴容原理)等在大佬博客都有
1. Q:爲什麼初始容量是16,且希望擴容時容量改變是2^n?
A:因爲對於一個2^n的數來說 當希望獲得hash後的散列值時有如下便利:
x=2^n時有個特性:當將對key 的 hash 值(y)對桶個數(容量 x,且爲2^n時)取模時 : y % x == y & (x-1) 要知道位運算比取模運算快的多了,所以性能要好的多。比如:
x : 00010000
x-1 : 00001111
--
y : 10110010
x-1 : 00001111
y&(x-1) : 00000010
--
y : 10110010
x : 00010000
y%x : 00000010
至於爲什麼不直接用hashCode()那,是因爲若直接用hashcode值來運算最終得到的index結果,容易出現 哈希碼 與 數組大小範圍不匹配的情況:(hashcode最大值爲Integer.MAX_VALUE 即最高有32位),而hashmap的最大容量是2^30,
所以通過擾動處理確定在table中的位置,使得hash值高位歸於0,只保留低位作爲數組下標:
注:該函數僅存在於JDK 1.7 ,JDK 1.8中實際上無該函數(直接用1條語句判斷寫出),但原理相同
static int indexFor(int h, int length) {
return h & (length-1);
}
且若初始容量不等於2^n時,將會在構造函數裏轉化成2^n,.
HashMap(int initialCapacity, float loadFactor)
2. Q:你知道 hash 的實現嗎?爲什麼要這樣實現?
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
A:JDK 1.8 中,是通過 hashCode() 的高 16 位異或低 16 位實現的:(h = k.hashCode()) ^ (h >>> 16),主要是從速度,功效和質量來考慮的,減少系統的開銷,也不會造成因爲高位沒有參與下標的計算,從而引起的碰撞。重點防止不同hashCode的高位不同但低位相同導致的hash衝突。簡單點說,就是爲了把高位的特徵和低位的特徵組合起來,降低哈希衝突的概率,也就是說,儘量做到任何一位的變化都能對最終得到的結果產生影響。 擾動處理。
比如:(hashcode最大值爲Integer.MAX_VALUE 即最高有32位)
10011000 11111000
& 00001111 & 00001111
---------- ----------
00001000 00001000
若上式第一行爲直接的hashcode值爲hash值,則更容易造成衝突(碰撞),hashcode值的低幾位位數有限的。
Q:爲什麼要用異或運算符?
A:保證了對象的 hashCode 的 32 位值只要有一位發生改變,整個 hash() 返回值就會改變。儘可能的減少碰撞。
3. Q:HashMap多線程的條件競爭(爲什麼死鎖)
A:因爲HashMap是線程不安全的,所以如果當多個線程同時訪問,hashmap恰好達到最大容量,需要擴容rehash時容易發生死鎖問題,比如(1.7版的鏈表尾插發導致但 JDK 1.8
還是線程不安全,因爲 無加同步鎖保護):假設有兩個進程a、b,HashMap容量爲2, a線程須放入key值爲 A、B、C。D。在a線程中A、B的Hash值相同,於是利用頭插法插入該table的一個entry[]裏,假設爲A-->B,而C、D Hash值不同,但此時容量不足,需要擴容並且rehash,需要把數據從老的Hash表中 遷移到新的Hash表中(refresh)。這時b進程拿到CPU,a暫時阻塞,b進程也放入不同的key值的鍵值對,發現容量不足,也需要擴容並且rehash。refresh之後鏈表變爲B->A(因爲refresh後插在新的table表裏的鏈表相對於原來的鏈表是逆序的,然後a進程活了繼續運行,還拿着之前的鏈表結構爲A->B,這時就形成A.next=B,B.next=A的環形鏈表。一旦取值進入這個環形鏈表就會陷入死循環。 這個有圖形解釋https://blog.csdn.net/qq_37113604/article/details/81353626最後面