【Java】HashMap學習筆記

1. 什麼是HashMap

HashMap採用Entry數組來存儲key-value對,每一個鍵值對組成了一個Entry實體,Entry類實際上是一個單向的鏈表結構,它具有Next指針,可以連接下一個Entry實體。 只是在JDK1.8中,鏈表長度大於8的時候,鏈表會轉成紅黑樹!

簡單來說:

  • HashMap是一種數據結構,表達得是鍵值對關係
  • JDK1.7中:數組+鏈表
    在這裏插入圖片描述
  • JDK1.8中:數組+鏈表+紅黑樹
    在這裏插入圖片描述

2. 數據結構分析

  • 數組是用來確定桶的位置,利用元素的key的hash值對數組長度取模得到,查找效率O(1);
  • 鏈表是用來解決hash衝突問題,當出現hash值一樣的情形,就在數組上的對應位置形成一條鏈表,查找效率O(n);
  • 紅黑樹是用來解決鏈表太長時查找效率低的問題,紅黑樹是平衡二叉樹的一種,查找效率O(log n)。

3. Put和Get過程

Put過程:

  • 對key的hashCode()做hash運算,計算index;
  • 如果沒碰撞直接放到bucket裏;
  • 如果碰撞了,以鏈表的形式存在buckets後;
  • 如果碰撞導致鏈表過長(大於等於TREEIFY_THRESHOLD),就把鏈表轉換成紅黑樹;
  • 如果節點已經存在就替換old value(保證key的唯一性)
  • 如果bucket滿了(超過loadfactor*current capacity),就要resize。

簡單來說:按照數組–>鏈表–>紅黑樹–>擴容的順序進行數據插入。

Get過程:

  • 對key的hashCode()做hash運算,計算index;
  • 如果在bucket裏的第一個節點裏直接命中,則直接返回;
  • 如果有衝突,則通過key.equals(k)去查找對應的Entry。

4. 擴容機制

當發生哈希衝突並且size大於閾值(loadfactor*current capacity)的時候,需要進行數組擴容,擴容時,需要新建一個長度爲之前數組2倍的新的數組,然後將當前的Entry數組中的元素全部傳輸過去(按照Put過程進行),擴容後的新數組長度爲之前的2倍,所以擴容相對來說是個耗資源的操作。

爲什麼擴容是2的次冪?

HashMap的數組長度一定保持2的次冪,比如16的二進制表示爲 10000,那麼length-1就是15,二進制爲01111,同理擴容後的數組長度爲32,二進制表示爲100000,length-1爲31,二進制表示爲011111。

  • 保證得到的新的數組索引和老數組索引一致
    在這裏插入圖片描述
  • 會使得獲得的數組索引index更加均勻
    在這裏插入圖片描述
  • 使得低位更加散列,任何一位的變化都會對結果產生影響
    在這裏插入圖片描述
  • 減小衝突概率,提高資源利用率
    如果不是2的次冪,也就是低位不是全爲1此時,要使得index=21,h的低位部分不再具有唯一性了,哈希衝突的機率會變的更大,同時,index對應的這個bit位無論如何不會等於1了,而對應的那些數組位置也就被白白浪費了。

5. JAVA HASHMAP的死循環

簡單而言
HashMap不是線程安全的,若是併發的進行ReHash,可能因爲種種巧合,使得在鏈表中出現環形鏈接,一旦進行Get操作,將陷入死循環。以下是來自coolshell的一個例子,詳情請閱讀原文。

正常的ReHash的過程

  • 我假設了我們的hash算法就是簡單的用key mod 一下表的大小(也就是數組的長度)
  • 最上面的是old hash表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以後都衝突在table[1]這裏了。
  • 接下來的三個步驟是Hash表 resize成4,然後所有的<key,value> 重新rehash的過程
    在這裏插入圖片描述

併發下的Rehash
1)假設我們有兩個線程。而我們的線程二執行完成了。於是我們有下面的這個樣子。
在這裏插入圖片描述
注意,因爲Thread1的 e 指向了key(3),而next指向了key(7),其在線程二rehash後,指向了線程二重組後的鏈表。我們可以看到鏈表的順序被反轉後。

2)線程一被調度回來執行。

先是執行 newTalbe[i] = e;
然後是e = next,導致了e指向了key(7),
而下一次循環的next = e.next導致了next指向了key(3)
在這裏插入圖片描述
3)一切安好。
線程一接着工作。把key(7)摘下來,放到newTable[i]的第一個,然後把e和next往下移。
在這裏插入圖片描述
4)環形鏈接出現。

e.next = newTable[i] 導致 key(3).next 指向了 key(7)

注意:此時的key(7).next 已經指向了key(3), 環形鏈表就這樣出現了。
在這裏插入圖片描述
於是,當我們的線程一調用到,HashTable.get(11)時,悲劇就出現了——Infinite Loop。

JAVA HASHMAP的死循環
HashMap面試指南
Java集合之一—HashMap

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章