注:本文所有代碼示例均基于 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個值后,并沒有執行擴容操作,容量、加載因子、閥值均沒有變化
