HashMap 擴容 加載因子


HashMap:

public HashMap(int initialCapacity, float loadFactor) {
    //初始容量不能<0
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: "
                + initialCapacity);
    //初始容量不能 > 最大容量值,HashMap的最大容量值為2^30
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //負載因子不能 < 0
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: "
                + loadFactor);
    // 計算出大於 initialCapacity 的最小的 2 的 n 次方值。
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
    this.loadFactor = loadFactor;
    //設置HashMap的容量極限,當HashMap的容量達到該極限時就會進行擴容操作
    threshold = (int) (capacity * loadFactor);
    //初始化table數組
    table = new Entry[capacity];
    init();
}

在這里提到了兩個參數:初始容量,加載因子。

這兩個參數是影響HashMap性能的重要參數,其中容量表示哈希表中桶的數量,初始容量是創建哈希表時的容量,

加載因子是哈希表在其容量自動增加之前可以達到多滿的一種尺度,它衡量的是一個散列表的空間的使用程度,負載因子越大表示散列表的裝填程度越高,反之愈小。

對於使用鏈表法的散列表來說,查找一個元素的平均時間是O(1+a),因此如果負載因子越大,對空間的利用更充分,然而后果是查找效率的降低;

如果負載因子太小,那么散列表的數據將過於稀疏,對空間造成嚴重浪費。系統默認負載因子為0.75,一般情況下我們是無需修改的。

加載因子:

 loadFactor

 

擴容:

void addEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold) // 這里是關鍵,一旦大於等於threshold的數值
            resize(2 * table.length); // 將會引起容量2倍的擴大
    }
void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity]; // 新的容器空間
        transfer(newTable); // 復制數據過去
        table = newTable;
        threshold = (int)(newCapacity * loadFactor); // 重新計算threshold的值
    }
void transfer(Entry[] newTable) {
         // 保留原數組的引用到src中,
         Entry[] src = table;
         // 新容量使新數組的長度
         int newCapacity = newTable.length;
      // 遍歷原數組
         for (int j = 0; j < src.length; j++) {
             // 獲取元素e
             Entry<K,V> e = src[j];
             if (e != null) {
                 // 將原數組中的元素置為null
                 src[j] = null;
                 // 遍歷原數組中j位置指向的鏈表
                 do {
                     Entry<K,V> next = e.next;
                     // 根據新的容量計算e在新數組中的位置
                     int i = indexFor(e.hash, newCapacity);
                     // 將e插入到newTable[i]指向的鏈表的頭部
                     e.next = newTable[i];
                     newTable[i] = e;
                     e = next;
                 } while (e != null);
             }
         }
     }

通過上面的transfer方法可以看出,

e.next=newTable[i];

newTable[i]=e;

鏈表存儲倒過來了,最先出來的會將其next指向null,后面的就指向前一個,當然數據只有原來的一部分。

===================================================================

隨着HashMap中元素的數量越來越多,發生碰撞的概率就越來越大,所產生的鏈表長度就會越來越長,這樣勢必會影響HashMap的速度,

為了保證HashMap的效率,系統必須要在某個臨界點進行擴容處理。

該臨界點在當HashMap中元素的數量等於table數組長度*加載因子。

但是擴容是一個非常耗時的過程,因為它需要重新計算這些數據在新table數組中的位置並進行復制處理。

所以如果我們已經預知HashMap中元素的個數,那么預設元素的個數能夠有效的提高HashMap的性能。

問題:

當重新調整HashMap大小的時候,確實存在條件競爭,因為如果兩個線程都發現HashMap需要重新調整大小了,它們會同時試着調整大小。

在調整大小的過程中,存儲在鏈表中的元素的次序會反過來,因為移動到新的bucket位置的時候,HashMap並不會將元素放在鏈表的尾部,而是放在頭部,這是為了避免尾部遍歷(tail traversing)。

如果條件競爭發生了,那么就死循環了。

對於高並發情況下的擴容,下面有篇文章講解的很好

http://mp.weixin.qq.com/s?__biz=MzIxMjE5MTE1Nw==&mid=2653192000&idx=1&sn=118cee6d1c67e7b8e4f762af3e61643e&chksm=8c990d9abbee848c739aeaf25893ae4382eca90642f65fc9b8eb76d58d6e7adebe65da03f80d&scene=21#wechat_redirect

鏈接引用:

http://www.javacui.com/Theory/377.html

http://blog.csdn.net/zhangerqing/article/details/8193118

http://www.cnblogs.com/matrix-skygirl/archive/2013/01/17/2864919.html


免責聲明!

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



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