摘要:結合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在此祝各位生活愉快!工作順利!
人情早晚有用完的時候,如若自己擁有足夠的實力,定能贏得別人的尊重。更何況沒有人欠我們人情呢!為人處世尚且如此,披星戴月的碼農是不是要刻苦鑽研,拓展技術棧的廣度和深度呢?