HashMap擴容全過程


   1.如果HashMap的大小超過了負載因子(load factor)定義的容量,怎么辦?

默認的負載因子大小為0.75,也就是說,當一個map填滿了75%的bucket時候,和其它集合類(如ArrayList等)一樣,將會創建原來HashMap大小的兩倍的bucket數組,來重新調整map的大小,並將原來的對象放入新的bucket數組中。這個過程叫作rehashing,因為它調用hash方法找到新的bucket位置。這個值只可能在兩個地方,一個是原下標的位置,另一種是在下標為<原下標+原容量>的位置

  2.重新調整HashMap大小存在什么問題嗎?

  • 當重新調整HashMap大小的時候,確實存在條件競爭,因為如果兩個線程都發現HashMap需要重新調整大小了,它們會同時試着調整大小。在調整大小的過程中,存儲在鏈表中的元素的次序會反過來,因為移動到新的bucket位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是為了避免尾部遍歷(tail traversing)。如果條件競爭發生了,那么就死循環了。(多線程的環境下不使用HashMap)
  • 為什么多線程會導致死循環,它是怎么發生的?

  HashMap的容量是有限的。當經過多次元素插入,使得HashMap達到一定飽和度時,Key映射位置發生沖突的幾率會逐漸提高。這時候,HashMap需要擴展它的長度,也就是    進行Resize。

  Resize是什么?首先我們先認識2個變量 

  1.Capacity

  HashMap的當前長度。HashMap的長度是2的冪。

  2.LoadFactor

  HashMap負載因子,默認值為0.75f。

  衡量HashMap是否進行Resize的條件如下:

  HashMap.Size >= Capacity * LoadFactor

   Resize步驟

  1.擴容:創建一個新的Entry空數組,長度是原數組的2倍。

  2.ReHash:遍歷原Entry數組,把所有的Entry重新Hash到新數組。為什么要重新Hash呢?因為長度擴大以后,Hash的規則也隨之改變。

  hash公式:index = HashCode(Key) & (Length - 1)

  我們假設rehash之前的HashMap是這樣的

  

 

  那么rehash之后可能是這樣

  

 

  代碼是這樣的

  

/**
 * Transfers all entries from current table to newTable.
 */
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

  現在假設一個場景,有一個hashmap如下

  

當有A,B這兩個線程要對該hash map進行put操作

此時由於空間的不足,該hashmap必將進行擴容

假如此時線程B遍歷到Entry3對象,剛執行完紅框里的這行代碼,線程就被掛起。對於線程B來說:

e = Entry3

next = Entry2

這時候線程A暢通無阻地進行着Rehash,當ReHash完成后,結果如下(圖中的e和next,代表線程B的兩個引用):

直到這一步,看起來沒什么毛病。接下來線程B恢復,繼續執行屬於它自己的ReHash。線程B剛才的狀態是:

 

e = Entry3

next = Entry2

 

我們繼續執代碼,Entry3放入了線程B的數組下標為3的位置,並且e指向了Entry2。此時e和next的指向如下:

e = Entry2

next = Entry2

 

接下來用頭插法把Entry2插入到了線程B的數組的頭結點

e = Entry2

next = Entry3

e = Entry3

next = Entry3.next = null

 

newTable[i] = Entry2這里若果是正常情況是newTable[i] =null,但是由於Entry2的hash被定為帶同一個數組地址

e = Entry3

Entry2.next = Entry3

Entry3.next = Entry2

鏈表出現了環形!導致了死循環(多線程下請使用CocurrentHashMap)

 

更新一下這個比較易懂

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM