jdk HashMap 1 7 補充文章

2021-08-21 07:26:27 字數 3726 閱讀 8827

此篇是關於初期的一篇hashmap文章的補充文章:主要涉及兩個東西,一、擴容;二、擴容時的執行緒安全分析。

hashmap1.7

在上述篇幅裡分析了hash過程,put過程和get過程。應該來說還是比較詳細的。

一、擴容

擴容應該是hashmap內乙個非常常見的問題。此篇還是基於1.7去補充下,1.8的稍微複雜了一些是由於引入了紅黑樹進去。

void addentry(int hash, k key, v value, int bucketindex) 

createentry(hash, key, value, bucketindex);

}

當put的時候addentry方法內存在乙個擴容的判斷:

1.當size>=threshold時(通俗的講就是當前個數是否大於閾值);

2.當前存在hash衝突了;

threshold = (int)math.min(capacity * loadfactor, maximum_capacity + 1);
capacity初始預設值是16,loadfactory預設是0.75,也就是threshold預設是16*0.75=12。當個數大於12時,理論上就需要擴容了。

場景1:map中的陣列初始大小是16,那麼放進去的12個資料都放在了不同的陣列內(假設是0-11的位置上),這樣,當第13個放進來的時候(如果hash之後的位置是0-11(hash衝突了)),就需要擴容了。

場景2:map中的陣列初始大小是16,那麼放進去的12個資料都放在了不同的陣列內(假設是0-11的位置上),這樣,當第13個放進來的時候(如果hash之後的位置是12(hash沒有衝突)),那麼此時是不需要擴容的。這種情況下的極端例子就是16個資料在放置的時候都依次放在了16位的陣列中(0-15),這樣當17個資料來的時候才會擴容。

那麼在最初最多能存放多少資料而不發生擴容呢?

場景3:場景3更加極端一些,初始大小是16,閾值是12,那麼假設前11個值都落到了位置0上,也就是儲存到了陣列的同乙個位置上,後續存入的15個資料都依次存放在1-15中(此時資料雖然大於閾值,但是沒有發生哈市衝突,所以不擴容),當第27個資料進來時,已經沒有位置了,必定發生衝突導致擴容。,所以最大的資料是11+15=26個資料

擴容後續**

resize:

1.擴容有最大值限定,2^30方。

2.transfer就是將原陣列的值放入新陣列中。

3.最後重新設定threshold(新的閾值)。

void resize(int newcapacity) 

entry newtable = new entry[newcapacity];

boolean oldalthashing = usealthashing;

usealthashing |= sun.misc.vm.isbooted() &&

(newcapacity >= holder.alternative_hashing_threshold);

boolean rehash = oldalthashing ^ usealthashing;

transfer(newtable, rehash);

table = newtable;

threshold = (int)math.min(newcapacity * loadfactor, maximum_capacity + 1);

}

transfer:

void transfer(entry newtable, boolean rehash) 

int i = indexfor(e.hash, newcapacity);

e.next = newtable[i];

newtable[i] = e;

e = next;}}

}

transfer內沒什麼特殊的東西,就是重新計算hash的值在新陣列中的哪乙個位置上。

這裡就引出了乙個新的問題,關於transfer的執行緒安全問題,也可以說是hashmap的執行緒安全問題,大家都知道hashmap是執行緒不安全的,那麼提現在哪兒呢?乙個就是put的時候,另乙個就是擴容裡的transfer的時候。

put就不說了,比較容易理解。今天主要分析一下transfer的時候的執行緒安全問題;

基礎前提:

1.陣列初始大小為2

2.hash演算法取簡單的key%length 的大小。

單執行緒場景:

多執行緒場景:

多執行緒存在問題主要會是在**呢?看單執行緒場景中,我們可以看見對於原陣列+鍊錶的操作,存在兩個指標,乙個e,乙個e.next。這就是問題所在(對於鍊錶的操作指標e,如果乙個執行緒完整操作之後,後續執行緒再次操作時,鍊錶的結構已經發生改變,那麼執行緒不安全也就無法避免)。

我們來看一看核心操作:

while(null != e) 

int i = indexfor(e.hash, newcapacity);

e.next = newtable[i];

newtable[i] = e;

e = next;

}

問題就出現在步驟1處,假設現在存在兩個執行緒a,執行緒b同時執行put操作。執行緒a執行到步驟1時,掛了。執行緒b正常執行。

因此執行緒a和執行緒b會出現下面的場景:

執行緒a再次被喚醒繼續執行擴容:

第一次迴圈,此時e指向key=3的節點,e.next指向key=7的節點。因此最終的結果就是執行緒a的位置3指向了key=3的處於執行緒b中的節點。

第二次迴圈,注意此時e和e.next的位置變化。這個時候e指向的是key=7,對於執行緒a來說當前存在指向key=3的資料,因此,key=7的next指向了key=3的節點,而key=7就變成了執行緒a的頭節點。

第三次迴圈,注意此時e又指向了key=3的節點,而e.next指向了null節點。如果針對key=3的節點再次操作的話,如下關鍵語句:

e.next = newtable[i];
key=3的next指向了第二次迴圈時的鍊錶開頭資料key=7。所以就形成了乙個環形結構,table[3]->key[3]->key[7]->[3]。這就是在多執行緒下可能出現的場景。

Python第17天筆記 檔案操作指令 補充

find 搜尋方法 find 搜尋目錄 name iname 搜尋字元 name 區分大小寫 iname 不區分大小寫 find home name city college demo.txt find home iname city college demo.txt精準搜尋 模糊搜尋 根據檔案大小...

Python基礎11 檔案處理補充

python基礎11 檔案處理補充 檔案操作的簡單方法 控制檔案內指標的移動 檔案開啟模式分類兩大類 1 r 唯讀 在檔案不存在時則報錯,檔案存在時檔案指標會跳到檔案開頭 f open a.txt mode rt encoding utf 8 res f.read print f.readable ...

17 文字屬性和字型屬性

介紹幾個常用的。文字對齊 text align 屬性規定元素中的文字的水平對齊方式。屬性值 none center left right justify 文字顏色 color屬性 文字首行縮排 text indent 屬性規定元素首行縮排的距離,單位建議使用em 文字修飾 text decorati...