1. 程式人生 > >java集合--HashMap(四)

java集合--HashMap(四)

java集合–HashMap(四)

1.HashMap的resize()方法

     final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return
oldTab; } 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 { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float
)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }

HashMap 在進行擴容時,使用的 rehash 方式非常巧妙,因為每次擴容都是翻倍,與原來計算(n-1)&hash 的結果相比,只是多了一個 bit 位,所以節點要麼就在原來的位置,要麼就被分配到“原位置+舊容量”這個位置。例如,原來的容量為 32,那麼應該拿 hash 跟 31(0x11111)做與操作;在擴容擴到了 64 的容量之後,應該拿 hash 跟 63(0x111111)做與操作。新容量跟原來相比只是多了一個 bit 位,假設原來的位置在 23,那麼當新增的那個 bit 位的計算結果為 0時,那麼該節點還是在 23;相反,計算結果為 1 時,則該節點會被分配到 23+31 的桶上。正是因為這樣巧妙的 rehash 方式,保證了 rehash 之後每個桶上的節點數必定小於等於原來桶上的節點數,即保證了 rehash 之後不會出現更嚴重的衝突。

2.HashMap的遺留小問題

  • 在java1.8以後,當插入值時如果出現衝突,會以連結串列的形式放在後面,如果數量超過8個就會轉化為紅黑樹。同樣的道理,當刪除的時候,如果數量小於6個就會恢復鏈式結構。因為紅黑樹的平均查詢長度為(log n),連結串列的平均查詢長度為n/2。d當長度為8時,紅黑樹平均查詢長度為3。如果使用連結串列,平均查詢程度高度為4。所以8是連結串列轉化為紅黑樹的臨界點。如果長度在6以內,是沒有必要轉化的,況且連結串列轉化為紅黑樹也是需要時間的。

3.HashMap的小總結

  • hashCode()和equals()方法是理解HashMap的基石,很重要。重寫equals()方法就必須重寫hashCode()方法。

  • HashMap可以允許null作為key和value.

  • HashMap是非線性安全的,只適用於單執行緒環境下。在多執行緒情況下可以使用ConcurrentHashMap,或者可以使用Collections中的synchronizedMap()方法將HashMap轉化為執行緒安全。

  • ConcurrentHashMap是採用分段鎖的機制,它的效率比Hashtable高,且是執行緒安全的。具體什麼原理,建議讀者去自己去查詢資料,這裡不再進行闡述,或者在後面的文章中我會更新。