有幾個重要的常量:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//默認的桶數組大小
static final int MAXIMUM_CAPACITY = 1 << 30;//極限值(超過這個值就將threshold修改爲Integer.MAX_VALUE(此時桶大小已經是2的31次方了),表明不進行擴容了)
static final float DEFAULT_LOAD_FACTOR = 0.75f;//負載因子(請閱讀下面體會這個值的用處)
(一)調用HashMap()構造方法
這個構造方法長這樣:
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; //設置一個默認的負載因子,默認爲0.75,下面說它有啥用
}
這就完了,然後用於hash的桶數組爲null,當我們第一次put的時候,會到達這段代碼:
Node<K,V>[] tab; int n;
//table就是桶數組,初始爲null
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
所以會進入resize()方法,到達這段代碼:
//取消多餘的代碼,我直接貼上進入的這個分支的代碼
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
newCap的意思是:即將用於hash的桶數組的長度,這裏的默認值是16
newThr的意思是:當桶中的鍵值對的個數超過這個值時就進行擴容,這時候上面提到的負載因子就起作用了,所以這裏的newThr爲0.75×16 = 12
所以經過這個默認構造方法,並且進行第一次put後,桶數組建立了,桶容量爲16,極限值爲12。
(二)調用HashMap(int initialCapacity)構造方法
public HashMap(int initialCapacity) {
//這個方法實際調用另一個構造方法,所以這個構造方法就不分析了,直接看第三個的分析
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
(三)調用HashMap(int initialCapacity, float loadFactor)構造方法
public HashMap(int initialCapacity, float loadFactor) {
//桶數組的大小小於0時拋異常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果桶數組的大小超過最大值,則簡單的將桶容量修改爲最大值2的30次方
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//如果負載因子不符合規範,那麼拋異常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
//這個意思是根據桶數組的大小求一個“極限值threshold”,當桶中的鍵值隊個數超過這個大小就進行擴容
//tableSizeFor方法求得的數字是剛超過initialCapacity的一個2的n次方的數,例如initialCapacity是1000,那麼得到的threshold就是1024
this.threshold = tableSizeFor(initialCapacity);
}
所以經過這個方法之後,桶容量確定了,極限值也確定了,但是桶數組還是null。
當第一次put時,觸發如下代碼:
//發現桶數組是null,所以需要新建一個桶數組
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
接着看resize()中觸發了哪些代碼:
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;//看這裏,oldCap會是0
int oldThr = threshold;//看這裏,oldThr不會是0,因爲有tableSizeFor()方法,確保oldThr至少是1
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 >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//所以肯定會進入這個分支,將桶數組的大小改爲極限值大小。然後在下面創建一個newCap大小的桶數組
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
然後還會進入下面這個分支:
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
這個分支會按負載因子設置極限值
(四)超過極限值後的擴容
在每次put之後,會有下面這個判斷:
if (++size > threshold)
resize();
就是說超過極限值時會進行擴容,擴容方式如下:
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
//肯定會進入這個分支
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//如果我們是這樣調用:HashMap(7,任意滿足條件的值),那麼經過第一次調用put()會初始化桶數組的大小和極限值一樣,這裏即爲8。所以這裏不會進入這個分支,但是newCap會變爲16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//所以會進入這裏
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;//計算的出這裏的threshold即極限值會由剛纔的8變爲12(和負載因子大小有關)
接上面這個例子,下一次擴容會如何?
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//因爲桶容量變爲了16,所以這次會進入這個分支,桶容量和極限值都會加倍。
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
直到…………
當桶容量加倍到最大值會怎麼樣?
//因爲每次都是加倍,所以最終肯定會加倍到MAXIMUM_CAPACITY,會進入這個分支。
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
桶容量變爲最大值時,緊接着的一次擴容只是簡單的將極限值修改爲Integer.MAX_VALUE。桶數組大小並不會繼續加倍
(五)如果HashMap中的鍵值對數量超過Integer.MAX_VALUE了呢?
我們仔細看一下這段代碼:
//這個size類型是int,那麼最大就是Integer.MAX_VALUE,所以不會有++size > threshold的情況,往後都不會進行擴容了。
if (++size > threshold)
resize();