HashMap中的resize問題

2022-05-24 04:48:11 字數 3786 閱讀 1153

在jdk1.8中,hashmap的resize()函式做了相應的調整,尤其是對於在buckets的鍊錶中,官方給出的該resize()函式主要在兩種情況下使用:

初始化的時候

將雜湊表擴容成之前的兩倍時

下面首先看初始化時,實際的resize()函式做了哪些工作:

final node resize() )

node newtab = (node)new node[newcap];

table = newtab;

return newtab;

}

從**邏輯來看,初始化時resize()就是將 threshold=12,以及 table=new node[16];

當雜湊表需要擴容時:

final node resize() 

else if ((newcap = oldcap << 1) < maximum_capacity &&

oldcap >= default_initial_capacity)

newthr = oldthr << 1; // double threshold

}else if (oldthr > 0) // initial capacity was placed in threshold

newcap = oldthr;

else

// 想不出什麼時候會出現

if (newthr == 0)

threshold = newthr; // threshold=2*oldthreshold

@suppresswarnings()

node newtab = (node)new node[newcap];

table = newtab;

// 將原來雜湊表中的資料移到新的table裡面

if (oldtab != null)

else

} while ((e = next) != null);

if (lotail != null)

if (hitail != null) }}

}}return newtab;

}

可以看到,在對雜湊表擴容後,resize()還做了乙個最重要的工作,就是將原來table中的資料轉移到新的table當中,大概有三種情況:

oldtable[i]處只有乙個元素e: 轉到新的newtable中,位置應該為:newtable[e.hash&(newcap -1)]

oldtable[i]處為樹節點:之後在討論

oldtable[i]後接了乙個鍊錶:重點討論

如下圖:

在擴容時,newtable的容量變為原來的兩倍,要把鍊錶上的元素遷移到newtable上,需要按照e.hash & (newcap -1)計算出該元素在newtable上的哪個bucket裡面。

由於hashmap的容量總是2的倍數,那麼在計算新的索引位置時,與操作的結果就是將原來元素的hash值再高一位與1進行&操作,為0的結果為0,為1的結果為1. 當hash值高一位為0時,在newtable的索引與之前的一樣;當hash值高一位為1時,newtable的索引相當於oldtable上的索引+ oldtable的長度。可以參照上面的22和38.

所以,官方給出jdk1.8中resize()的注釋是:經過rehash之後,元素的位置要麼是在原位置,要麼是在原位置再移動2次冪的位置。

為了判斷原來hash值高一位是0還是1,jdk1.8直接(e.hash & oldcap)來判斷,結果等於0原來高一位就是0,否則就是1。這樣正好也就形成了兩個新的鍊錶,lohead-->lotail還在原來的bucket中,hihead--->hitail處於新的位置。新的鍊錶的順序和原來的一致。

在jdk1.7中,resize()方法與1.8大體類似,也是擴容,具體:

void resize(int newcapacity) 

entry newtable = new entry[newcapacity]; //初始化乙個新的entry陣列

transfer(newtable); //!!將資料轉移到新的entry陣列裡

table = newtable; //hashmap的table屬性引用新的entry陣列

threshold = (int) (newcapacity * loadfactor);//修改閾值

}

其中transfer()函式是將oldtable元素轉移到newtable中,具體實現:

void transfer(entry newtable)  while (e != null);}}

}

比較難理解的是e.next=newtable[i]; newtable[i] = e; e=next;幾個操作:

i是e元素在newtable中的索引位置,這幾個操作相當於是把e這個元素放在newtable[i]位置上,原來newtable[i]處有元素的話,按照鍊錶往後排,也就是相當於新元素插在鍊錶的頭位置

在jdk1.7版本當中,因為e.next與e的問題,所以在多執行緒中由於併發問題形成有環鏈表,在get查詢時可能會形成死迴圈。

其形成過程如下:

存在乙個hashmap如圖左側,在索引為3的位置有鍊錶a-->b,當擴容時resize為右圖所示,恰好a,b還處於新的索引位置7, 不過按照1.7順序正好顛倒;

多執行緒時,假設執行緒1和執行緒2同時執行,都會建立新的陣列,但是執行緒2執行到 next=e.next時,cpu切換到執行緒1上,如下圖,此時e指向a,next指向b;

執行緒1上述操作正常的transfer了oldtable,但是還沒有執行table=newtable,此時執行緒1切換到執行緒2,狀態如下:

執行緒2繼續執行之後的邏輯:

entrynext = e.next;

int i = indexfor(e.hash, newcapacity); //!!重新計算每個元素在陣列中的位置

e.next = newtable[i]; //標記[1]

newtable[i] = e; //將元素放在陣列上

由上可見,jdk1.7中多執行緒能形成環狀,除了沒有相應同步機制的原因,主要因為有乙個倒序(每次把元素指向bucket首位)的問題。在jdk1.8中多執行緒不會出現上述resize後成為有環鏈表的問題,但是多執行緒的本質問題還是存在。

HashMap的擴容機制 resize

hashmap底層邏輯 當我們往hashmap中put元素的時候,先根據key的hash值得到這個元素在陣列中的位置 即下標 然後就可以把這個元素放到對應的位置中了。如果這個元素所在的位子上已經存放有其他元素了,那麼在同乙個位子上的元素將以鍊錶的形式存放,新加入的放在鏈頭,最先加入的放在鏈尾。從ha...

HashMap中的resize以及死鏈的情況

說到hashmap中死鎖的情況,我們就必須要先講下resize 方法,顧名思義,這個方法就是來擴容的。當hashmap的size超過 thredshold時,就需要擴容了。當我們put時 截圖 為jdk hashmap原始碼 首先,我們需要知道幾個最基本的概念 entry table的初始化長度le...

HashMap中的resize以及死鏈的情況

之前我已經寫過關於hashmap的內容了 我們都知道hashmap是執行緒不安全的,如果多執行緒來訪問會有什麼問題呢?答案是會造成死鎖。接下來我們就分析下為何會造成死鎖。說到hashmap中死鎖的情況,我們就必須要先講下resize 方法,顧名思義,這個方法就是來擴容的。當hashmap的size超...