原文地址:https://xeblog.cn/articles/26
注:本文所有代碼示例均基于
JDK8
拄踪。
從源碼出發(fā)
默認值
通過查看 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;
構(gòu)造方法
/**
* 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
}
一般情況下都是通過這三種構(gòu)造方法來初始化 HashMap
的呀打。通過默認的無參構(gòu)造方法初始化后 HashMap
的容量就是默認的16鉴分,加載因子也是默認的0.75;通過帶參數(shù)的構(gòu)造方法可以初始化一個自定義容量和加載因子的 HashMap
惧蛹。通常情況下扇救,加載因子使用默認的0.75就好。
初始容量
HashMap
的容量要求必須是2的N次冪香嗓,這樣可以提高散列的均勻性迅腔,降低 Hash
沖突的風險。但是容量可以通過構(gòu)造方法傳入的靠娱,如果我傳入一個非2次冪的數(shù)進去呢沧烈?比如3?傳進去也不會干嘛呀饱岸,又不報錯掺出。。苫费。哈哈哈哈汤锨。
是的,不會報錯的百框,那是因為 HashMap
自己將這個數(shù)轉(zhuǎn)成了一個最接近它的2次冪的數(shù)闲礼。這個轉(zhuǎn)換的方法是 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;
}
這個方法會將傳入的數(shù)轉(zhuǎn)換成一個2次冪的數(shù),比如傳入的是3柬泽,則返回的是4慎菲;傳入12,則返回的是16锨并。
加載因子
加載因子和 HashMap
的擴容機制有著非常重要的聯(lián)系露该,它可以決定在什么時候才進行擴容。HashMap
是通過一個閥值來確定是否擴容第煮,當容量超過這個閥值的時候就會進行擴容解幼,而加載因子正是參與計算閥值的一個重要屬性,閥值的計算公式是 容量 * 加載因子
包警。如果通過默認構(gòu)造方法創(chuàng)建 HashMap
撵摆,則閥值為 16 * 0.75 = 12
,就是說當 HashMap
的容量超過12的時候就會進行擴容害晦。
/**
* The next size value at which to resize (capacity * load factor).
*
* @serial
*/
int threshold;
這是 HashMap
的 putVal(...)
方法的一個片段特铝,put(...)
方法其實就是調(diào)用的這個方法,size
是當前 HashMap
的元素個數(shù)壹瘟,當元素個數(shù)+1后超過了閥值就會調(diào)用 resize()
方法進行擴容鲫剿。
if (++size > threshold)
resize();
加載因子在一般情況下都最好不要去更改它,默認的0.75是一個非忱睿科學的值牵素,它是經(jīng)過大量實踐得出來的一個經(jīng)驗值。當加載因子設(shè)置的比較小的時候澄者,閥值就會相應(yīng)的變小笆呆,擴容次數(shù)就會變多,這就會導致 HashMap
的容量使用不充分粱挡,還沒添加幾個值進去就開始進行了擴容赠幕,浪費內(nèi)存,擴容效率還很低询筏;當加載因子設(shè)置的又比較大的時候呢榕堰,結(jié)果又很相反,閥值變大了嫌套,擴容次數(shù)少了逆屡,容量使用率又提高了,看上去是很不錯踱讨,實際上還是有問題魏蔗,因為容量使用的越多,Hash
沖突的概率就會越大痹筛。所以莺治,選擇一個合適的加載因子是非常重要的廓鞠。
實踐檢驗真理
默認無參構(gòu)造方法測試
通過默認構(gòu)造方法創(chuàng)建一個 HashMap
,并循環(huán)添加13個值
當添加第1個值后谣旁,容量為16床佳,加載因子為0.75,閥值為12
當添加完第13個值后榄审,執(zhí)行了擴容操作砌们,容量變?yōu)榱?2,加載因子不變瘟判,閥值變?yōu)榱?4
有參構(gòu)造方法測試-只設(shè)置初始容量
創(chuàng)建一個初始容量為12(非2次冪數(shù))的 HashMap
怨绣,并添加1個值
創(chuàng)建一個初始容量為2的 HashMap
,并添加2個值
當添加完第1個值后,容量為2,加載因子為0.75猎莲,閥值為1
當添加完第2個值后奔则,執(zhí)行了擴容操作,容量變?yōu)?未蝌,加載因子為0.75驮吱,閥值為3
有參構(gòu)造方法測試-設(shè)置初始容量和加載因子
創(chuàng)建一個初始容量為2、加載因子為1的 HashMap
萧吠,并添加2個值
當添加完第1個值后左冬,容量為2,加載因子為1纸型,閥值為2
當添加完第2個值后拇砰,并沒有執(zhí)行擴容操作,容量狰腌、加載因子除破、閥值均沒有變化