Java基礎_HashMap面試總結

總結自一下博客,自己留作記錄及總結。

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最後面

 

 



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章