HashMap的初始容量和載入因子
注:本文所有程式碼示例均基於
JDK8
。
從原始碼出發
預設值
通過檢視 HashMap
的原始碼可以得知其預設的初始容量為 16
,預設的載入因子為 0.75
。
/**
* The default initial capacity - MUST be a power of two.
* 預設的初始容量(必須是2的N次冪),預設為2^4=16
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The load factor used when none specified in constructor.
* 預設的載入因子為0.75
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
複製程式碼
構造方法
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
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);
}
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity,DEFAULT_LOAD_FACTOR);
}
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
複製程式碼
一般情況下都是通過這三種構造方法來初始化 HashMap
的。通過預設的無參構造方法初始化後 HashMap
的容量就是預設的16,載入因子也是預設的0.75;通過帶引數的構造方法可以初始化一個自定義容量和載入因子的 HashMap
。通常情況下,載入因子使用預設的0.75就好。
初始容量
HashMap
的容量要求必須是2的N次冪,這樣可以提高雜湊的均勻性,降低 Hash
衝突的風險。但是容量可以通過構造方法傳入的,如果我傳入一個非2次冪的數進去呢?比如3?傳進去也不會幹嘛呀,又不報錯。。。哈哈哈哈。
是的,不會報錯的,那是因為 HashMap
自己將這個數轉成了一個最接近它的2次冪的數。這個轉換的方法是 tableSizeFor(int cap)
。
/**
* Returns a power of two size for the given target capacity.
*/
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;
}
複製程式碼
這個方法會將傳入的數轉換成一個2次冪的數,比如傳入的是3,則返回的是4;傳入12,則返回的是16。
載入因子
載入因子和 HashMap
的擴容機制有著非常重要的聯絡,它可以決定在什麼時候才進行擴容。HashMap
是通過一個閥值來確定是否擴容,當容量超過這個閥值的時候就會進行擴容,而載入因子正是參與計算閥值的一個重要屬性,閥值的計算公式是 容量 * 載入因子
。如果通過預設構造方法建立 HashMap
,則閥值為 16 * 0.75 = 12
,就是說當 HashMap
的容量超過12的時候就會進行擴容。
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
int threshold;
複製程式碼
這是 HashMap
的 putVal(...)
方法的一個片段,put(...)
方法其實就是呼叫的這個方法,size
是當前 HashMap
的元素個數,當元素個數+1後超過了閥值就會呼叫 resize()
方法進行擴容。
if (++size > threshold)
resize();
複製程式碼
載入因子在一般情況下都最好不要去更改它,預設的0.75是一個非常科學的值,它是經過大量實踐得出來的一個經驗值。當載入因子設定的比較小的時候,閥值就會相應的變小,擴容次數就會變多,這就會導致 HashMap
的容量使用不充分,還沒新增幾個值進去就開始進行了擴容,浪費記憶體,擴容效率還很低;當載入因子設定的又比較大的時候呢,結果又很相反,閥值變大了,擴容次數少了,容量使用率又提高了,看上去是很不錯,實際上還是有問題,因為容量使用的越多,Hash
衝突的概率就會越大。所以,選擇一個合適的載入因子是非常重要的。
實踐檢驗真理
預設無參構造方法測試
通過預設構造方法建立一個 HashMap
,並迴圈新增13個值
當新增第1個值後,容量為16,載入因子為0.75,閥值為12
當新增完第13個值後,執行了擴容操作,容量變為了32,載入因子不變,閥值變為了24
有參構造方法測試-只設定初始容量
建立一個初始容量為12(非2次冪數)的 HashMap
,並新增1個值
建立一個初始容量為2的 HashMap
,並新增2個值
當新增完第1個值後,容量為2,載入因子為0.75,閥值為1
當新增完第2個值後,執行了擴容操作,容量變為4,載入因子為0.75,閥值為3
有參構造方法測試-設定初始容量和載入因子
建立一個初始容量為2、載入因子為1的 HashMap
,並新增2個值
當新增完第1個值後,容量為2,載入因子為1,閥值為2
當新增完第2個值後,並沒有執行擴容操作,容量、載入因子、閥值均沒有變化