【HashMap擴容機制】


我是🌟廖志偉🌟,一名🌕Java開發工程師🌕、📝Java領域優質創作者📝、🎉CSDN博客專家🎉、🌹幕后大佬社區創始人🌹。擁有多年一線研發經驗,研究過各種常見框架中間件的底層源碼,對於大型分布式微服務、三高架構(高性能高並發高可用)有過實踐架構經驗。

🍊博主:java_wxid
🍊博主:Java廖志偉
🍊社區:幕后大佬


文章目錄


本文的大概內容:

HashMap擴容機制


將(k1,v1)直接放入Node類型的數組中,這個數組初始化容量是16,默認的加載因子是0.75。

HashMap有兩個參數影響其性能:初始容量和加載因子。
容量是哈希表中桶的數量,初始容量只是哈希表在創建時的容量。
加載因子其實是用來判斷當前HashMap<K,V>中存放的數據量。
當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行擴容、rehash操作(即重建內部數據結構),擴容后的哈希表將具有兩倍的原容量。hashmap初始化容量時候,對容量大小做的處理,保證初始化容量為最近的2的冪次方。

HashMap初始化容量非得是2的冪次方,2的倍數不行么,奇數不行么?

  • 2的冪次方:hashmap在確定元素落在數組的位置的時候,計算方法是(n - 1) & hash,n為數組長度也就是初始容量 。hashmap結構是數組,每個數組里面的結構是node(鏈表或紅黑樹),正常情況下,如果你想放數據到不同的位置,肯定會想到取余數確定放在那個數組里,計算公式:hash % n,這個是十進制計算。在計算機中, (n - 1) & hash,當n為2次冪時,會滿足一個公式:(n - 1) & hash = hash % n,計算更加高效。
  • 奇數不行:在計算hash的時候,確定落在數組的位置的時候,計算方法是(n - 1) & hash,奇數n-1為偶數,偶數2進制的結尾都是0,經過hash值&運算后末尾都是0,那么0001,0011,0101,1001,1011,0111,1101這幾個位置永遠都不能存放元素了,空間浪費相當大,更糟的是這種情況中,數組可以使用的位置比數組長度小了很多,這樣就會造成空間的浪費而且會增加hash沖突。

HashMap加載因子為什么是0.75?

如果加載因子比較大,擴容發生的頻率比較低,浪費的空間比較小,發生hash沖突的幾率比較大。比如,加載因子是1的時候,hashmap長度為128,實際存儲元素的數量在64至128之間時間段比較多,這個時間段發生hash沖突比較多,造成數組中其中一條鏈表比較長,會影響性能。

如果加載因子比較小,擴容發生的頻率比較高,浪費的空間比較多,發生hash沖突的幾率比較小。比如,加載因子是0.5的時候,hashmap長度為128,當數量達到65的時候會觸發擴容,擴容后為原理的256,256里面只存儲了65個浪費了。

綜合了一下,取了一個平均數0.75作為加載因子。當負載因子為0.75,時代入到泊松分布公式,計算出來長度為8時,概率=0.00000006,概率很小了,鏈表長度為8時轉紅黑樹。

可能引發的問題: HashMap實際使用過程中會出現一些性能問題以及線程安全問題。
hashmap
在JDK1.7中有二塊值得注意的地方。

  • 擴容的時候需要rehash操作,需要將所有的數據重新計算HashCode,然后賦給新的HashMap<K,V>,rehash的過程是非常耗費時間和空間的。
  • 當並發執行擴容操作時會造成環形鏈和數據丟失的情況,開多個線程不斷進行put操作,所以當舊鏈表遷移新鏈表的時候,如果在新表的數組索引位置相同,則鏈表元素會倒置(就是因為頭插),所以最后的結果打亂了插入的順序,就可能發生環形鏈和數據丟失的問題,引起死循環,導致CPU利用率接近100%。

hashmap
在JDK1.8中,對HashMap進行了優化。

  • 經過rehash之后元素的位置,要么是在原位置,要么是在原位置再移動2次冪的位置。HashMap的數組長度恆定為2的n次方,也就是說只會為16,32,64,128這種數。即便你給的初始值是13,最后數組長度也會變成16,它會取與你傳入的數最近的一個2的n次方的數。
    hashmap

擴容之后元素的位置是否改變,完全取決於紫色框框中的運算是為0還是1,為0則新位置與原位置相同,不需要換位置,不為零則需要換位置。

在擴充HashMap的時候,不需要像JDK1.7的實現那樣重新計算hash,只需要看看原來的hash值新增的那個bit是1還是0就好了,是0的話索引沒變,是1的話索引變成原位置+原數組長度。

為什么新的位置是原位置+原數組長度?

HashMap中運算數組的位置使用的是leng-1,對於初始長度為16的數組,擴容之后為32,對應的leng-1就是15,31。

舉例一:假設某個元素的hashcode為52,這個52與15運算做按位與運算的的結果是4,這個52與31做按位與運算的的結果是20,20不就是等於4+16嗎,剛好是原數組的下標+原數組的長度。
舉例二:假設某個元素的hashcode為100,100&15=4,100&31=4,對於HashCode為100的元素來說,擴容后與擴容前其所在數組中的下標均為4。

以上兩個例子證明了,經過rehash之后,元素的位置要么是在原位置,要么是在原位置加原數組長度的位置。

因為每次擴容會把原數組的長度*2,那么再二進制上的表現就是多出來一個1

比如原數組16-1二進制為0000 1111
那么擴容后的32-1的二進制就變成了0001 1111
再次擴容64-1就是0011 1111

擴容之后元素的位置是否改變則取決於與這個多出來的1的運算結果,運算結果為0則不需要換位置,運算結果為1則換新位置,新位置為老位置的高位進1。

既省去了重新計算hash值的時間,而且由於新增的1bit是0還是1,可以認為是隨機的,因此resize的過程,均勻的把之前的沖突的節點分散到新的bucket了。

  • 發生hash碰撞,不再采用頭插法方式,而是直接插入鏈表尾部,因此不會出現環形鏈表的情況,但是在多線程環境下,會發生數據覆蓋的情況,如果沒有hash碰撞的時候,它會直接插入元素。如果線程A和線程B同時進行put操作,剛好這兩條不同的數據hash值一樣,並且該位置數據為null,線程A進入后還未進行數據插入時掛起,而線程B正常執行,從而正常插入數據,然后線程A獲取CPU時間片,此時線程A不用再進行hash判斷了,線程A會把線程B插入的數據給覆蓋,導致數據發生覆蓋的情況,發生線程不安全。可參考【HashMap並發修改異常】

總結

以上就是今天要講的內容,還希望各位讀者大大能夠在評論區積極參與討論,給文章提出一些寶貴的意見或者建議📝,合理的內容,我會采納更新博文,重新分享給大家。

🙏四連 關注🔎點贊👍收藏⭐️留言📝

感謝大家的支持,用心寫博文分享給大家,你的支持(🔎點贊👍收藏⭐️留言📝)是對我創作的最大幫助。
🍊微信公眾號:南北踏塵
🍊主頁地址:java_wxid
🍊社區地址:幕后大佬

給讀者大大的話

我本身是一個很普通的程序員,放在人堆里,除了與生俱來的🌹盛世美顏🌹、所剩不多的發量,就剩下180的大高個了。就是我這樣的一個人,默默堅持寫博文也有好多年了,有句老話說的好,🌕牛逼之前都是傻逼式的堅持🌕。希望自己可以通過大量的作品,時間的積累,個人魅力、運氣和時機,可以打造屬於自己的🌟技術影響力🌟。同時也希望自己可以成為一個🎄懂技術🎄,🎄懂業務🎄,🎄懂管理🎄的綜合型人才,作為項目架構路線的總設計師,掌控全局的🌕團隊大腦🌕,技術團隊中的🍊絕對核心🍊是我未來幾年不斷前進的目標。


提示:以下都是資源分享,求個一鍵三連。

面試資料

福利大放送,🎉歡迎關注🔎點贊👍收藏⭐️留言📝,拜托了🙏,這對我真的很重要。
點擊:面試資料
提取碼:2021

200套PPT模板

福利大放送,🎉歡迎關注🔎點贊👍收藏⭐️留言📝,拜托了🙏,這對我真的很重要。
點擊:200套PPT模板
提取碼:2021

提問的智慧

福利大放送,🎉歡迎關注🔎點贊👍收藏⭐️留言📝,拜托了🙏,這對我真的很重要。
點擊:提問的智慧
提取碼:2021

Java開發學習路線

名稱 鏈接
JavaSE 點擊: JavaSE
MySQL專欄 點擊: MySQL專欄
JDBC專欄 點擊: JDBC專欄
MyBatis專欄 點擊: MyBatis專欄
Web專欄 點擊: Web專欄
Spring專欄 點擊: Spring專欄
SpringMVC專欄 點擊: SpringMVC專欄
SpringBoot專欄 點擊: SpringBoot專欄
SpringCould專欄 點擊: SpringCould專欄
Redis專欄 點擊: Redis專欄
Linux專欄 點擊: Linux專欄
Maven3專欄 點擊: Maven3專欄
Spring Security5專欄 點擊: Spring Security5專欄
更多專欄 更多專欄,請到 java_wxid主頁 查看

P5學習路線圖
p5學習路線圖P6學習路線圖
P6學習路線圖P7學習路線圖
P7學習路線圖P8學習路線圖
P8學習路線圖

以上四張圖詳細介紹了作為Java開發工作者所需要具備的知識技能,同學們學廢了嘛,有想法系統學習的同學可以私聊我,🎉歡迎關注🔎點贊👍收藏⭐️留言📝。
🍊博主:java_wxid
🍊博主:Java廖志偉
🍊社區:幕后大佬


免責聲明!

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



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