在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超...