HashMap如何計算初始化容量,最大容量是多少


摘要:結合HashMap源碼,介紹HashMap如何確定初始化容量,其最大容量是多少。

  更多關於HashMap的知識點,請戳《HashMap知識點梳理、常見面試題和源碼分析》。

  本文基於Java 17進行分析。

  什么是HashMap的容量?容量就是HashMap中的數組大小或者桶的數量,是由 capacity 這個參數確定的。初始容量只是哈希表在創建時的容量。

  大家都知道HashMap是采用的懶加載機制,也就是說在執行new HashMap()的時候,構造方法並沒有在構造出HashMap實例的同時也把HashMap實例里所需的數組給初始化。那么,什么時候才去初始化里面的數組呢?答案是在第一次用到數組的時候才會去初始化它,就是在向HashMap里面添加元素的時候。而初始化數組時,它的容量是怎么確定的呢?有兩種情況:
第一種是調無參構造函數初始化實例。此時默認的數組初始化長度就是16,在后續添加元素時,進行數組初始化。

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

  第二種是調用帶數組容量參數的構造函數。

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

或者


/**
 * 如果構造函數傳入的值大於該數,則替換成該數。
 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 */
static final int MAXIMUM_CAPACITY = 1 << 30; // ①
    /**
     * The next size value at which to resize (capacity * load factor).
     * 數組擴容的閾值
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    int threshold;

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY) // ② 判斷是否超過最大容量
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

  顯而易見,上面那個構造方法執行的時候調用的就是下一個構造方法 this(initialCapacity, DEFAULT_LOAD_FACTOR)。當你調用帶參構造器初始化一個指定數組容量的HashMap時,構造器會根據輸入的參數提前計算出數組實際的長度,這個值也是在首次添加元素時起作用。計算的邏輯在函數 tableSizeFor(int cap) 中,源碼如下:

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // ③ 判斷是否超過最大容量
}

  它對入參cap減一之后使用了無符號右移,然后進行或運算,將n-1得到的值轉成2進制之后,從1的最高位開始將低位全部轉化為1,再加1之后就可以得到一個2^n的數。

HashMap的最大容量是2^30

  HashMap的最大容量是多少?容量默認是16,也可以構造時傳入,最大值是1<<30,即2^30,這個在源碼①的注釋中已經明確說明。首先必須理解操作符 <<,它是左移操作符,表示對二進制進行左移。通常情況下,1 << x 等於 2^x

  上一節中②和③所標記的代碼表明,如果要存的元素數目大於 MAXIMUM_CAPACITY,HashMap方法還把數組大小capacity強制設置成 MAXIMUM_CAPACITY
  綜上所述,HashMap限制數組大小最大值有兩個地方,其一就是初始化時調用tableSizeFor()函數,它會將容量置為 2的冪次,並保證不超過MAXIMUM_CAPACITY。其二就是調用擴容函數resize()進行容量翻倍時。如果容量達到MAXIMUM_CAPACITY時允許再擴容,新數組的容量就是 1 << 31,這會造成整型溢出,故Integer.MAX_VALUE是HashMap的最終容量。

HashMap的最大擴容閾值是2^31-1

在擴容函數resize()中有一個強制設置閾值大小的代碼片段:

if (oldCap >= MAXIMUM_CAPACITY) {
    threshold = Integer.MAX_VALUE;
    return oldTab;
}

hreshold是HashMap所能容納的最大數據量的Node(鍵值對)個數,threshold = length * Load factor。也就是說,在數組定義好長度之后,負載因子越大,所能容納的鍵值對個數越多。

  在這里可以看到,其實 HashMap 擴容閾值threshold的最大值就是Integer.MAX_VALUE=2^31-1;

刷一道面試題

  今天看一個關於HashMap的性能問題:如果HashMap只裝載100個元素,new HashMap(int x)中x的最佳值是多少,為什么?

  答案:256。

  解析:題意是令加載因子取默認值0.75,此時HashMap的初始容量可以設為100/0.75 = 133.33,向上取整為134。
無論你的HashMap(int x)中的x設置為多少,HashMap的大小都是2n,而且2n是大於x的第一個數,故大於134的第一個2^n無疑是256。

結束語

  以上就是這篇文章的全部內容了,希望本文對道友的學習或者工作能帶來一定的幫助,如有疑問請留言交流。Wiener在此祝各位生活愉快!工作順利!

  人情早晚有用完的時候,如若自己擁有足夠的實力,定能贏得別人的尊重。更何況沒有人欠我們人情呢!為人處世尚且如此,披星戴月的碼農是不是要刻苦鑽研,拓展技術棧的廣度和深度呢?

Reference


免責聲明!

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



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