先看HashMap的定義:
public class HashMap<K,V>extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap是AbstractMap的子類,實現了Map接口。
HashMap() Constructs an empty HashMap with the default initial capacity (16) and the default load factor (0.75). |
HashMap(int initialCapacity) Constructs an empty HashMap with the specified initial capacity and the default load factor (0.75). |
HashMap(int initialCapacity, float loadFactor) Constructs an empty HashMap with the specified initial capacity and load factor. |
HashMap(Map<? extends K,? extends V> m) Constructs a new HashMap with the same mappings as the specified Map. |
總共給出了4中構造方法。
1.HashMap() 不帶參數,默認初始化大小為16,加載因子為0.75;
2.HashMap(int initialCapacity) 指定初始化大小;
3.HashMap(int initialCapacity ,float loadFactor)指定初始化大小和加載因子大小;
4.HashMap(Map<? extends K,? extends V> m) 用現有的一個map來構造HashMap。
先分析一下初始化代碼
public HashMap(int initialCapacity,float loadFactor){
if(initialCapacity<0) //初始化大小小於0,拋出異常
throw new IllegalArgumentException("Illegal initial capacity: "
+initialCapacity);
if(initialCapacity>MAXIMUM_CAPACITY)//初始大小最大為默認最大值
initialCapacity=MAXIMUM_CAPACITY;
if(loadFactor <=0|| Float.isNaN(loadFactor)) //加載因子要在0到1之間
throw new IllegalArgumentException("Illegal load factor: "
+loadFactor);
this.loadFactor =loadFactor;
this.threshold=tableSizeFor(initialCapacity);
//threshold是根據當前的初始化大小和加載因子算出來的邊界大小,
//當桶中的鍵值對超過這個大小就進行擴容
}
threshold=initialCapacity*loadFactor,桶中的鍵值對超過這個界限就把桶的容量變大。
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
這是第一次向桶中放入元素時(put方法)的一次擴容,因為初始化時並沒有指定桶的大小
每次調用put方法后,都會進行驗證,判斷是否需要擴容
if (++size > threshold)
resize();
在看一下擴容的代碼
Node<K,V>[] oldTab=table;
int oldCap=(oldTab==null)?0:oldTab.length;// 第一次擴容時舊的桶大小為0
int oldThr=threshold; //桶中鍵值對數量的邊界大小。
int newCap,newThr=0; //新的桶大小和邊界大小。
if(oldCap>0){ //這是后來擴容時進入的,第一次指定桶大小時不會進入這
if(oldCap>= MAXIMUM_CAPACITY){//最大只能這么大了
threshold=Integer.MAX_VALUE;
return oldTab;
}
else if((newCap=oldCap<<1)<MAXIMUM_CAPACITY&&
oldCap>=DEFALT_INITAL_CAPACITY)
newThr=oldThr<<1; //如果舊的桶大小擴大一倍還沒有超過最大值,
//就把舊的桶大小和新的邊界都乘2
}
else if(oldThr>0)
newCap=oldThr; //第一次擴容時會進入這里初始化桶的大小
if(newThr==0){ //根據初始化桶數組大小和加載因子已經是否越界來設置新邊界
float ft=(float)newCap*loadFactor;
newThr=(newCap<MAXIMUM_CAPACITY && ft<(float)MAXIMUM_CAPACITY?
(int)ft:Integer.MAX_VALUE);
}
下面我們來討論一個問題。
為什么默認初始化桶數組大小為16,為什么加載因子的大小為0.75,這兩個值的選取有什么特點。
通過看上面的代碼我們可以知道這兩個值主要影響的threshold的大小,這個值的數值是當前桶數組需不需要擴容的邊界大小,
我們都知道桶數組如果擴容,會申請內存空間,然后把原桶中的元素復制進新的桶數組中,這是一個比較耗時的過程。既然這樣,那為何不把這兩個值都設置大一些呢,threshold是兩個數的乘積,設置大一些就不那么容易會進行擴容了啊。
原因是這樣的,如果桶初始化桶數組設置太大,就會浪費內存空間,16是一個折中的大小,既不會像1,2,3那樣放幾個元素就擴容,也不會像幾千幾萬那樣可以只會利用一點點空間從而造成大量的浪費。
加載因子設置為0.75而不是1,是因為設置過大,桶中鍵值對碰撞的幾率就會越大,同一個桶位置可能會存放好幾個value值,這樣就會增加搜索的時間,性能下降,設置過小也不合適,如果是0.1,那么10個桶,threshold為1,你放兩個鍵值對就要擴容,太浪費空間了。