HashMap的容量與擴容
阿新 • • 發佈:2018-12-26
有幾個重要的常量:
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;//負載因子(請閱讀下面體會這個值的用處)
- 1
- 2
- 3
(一)呼叫HashMap()構造方法
這個構造方法長這樣:
public HashMap () {
this.loadFactor = DEFAULT_LOAD_FACTOR; //設定一個預設的負載因子,預設為0.75,下面說它有啥用
}
- 1
- 2
- 3
這就完了,然後用於hash的桶陣列為null,當我們第一次put的時候,會到達這段程式碼:
Node<K,V>[] tab; int n;
//table就是桶陣列,初始為null
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
- 1
- 2
- 3
- 4
所以會進入resize()方法,到達這段程式碼:
//取消多餘的程式碼,我直接貼上進入的這個分支的程式碼
else {
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
- 1
- 2
- 3
- 4
- 5
newCap的意思是:即將用於hash的桶陣列的長度,這裡的預設值是16
newThr的意思是:當桶中的鍵值對的個數超過這個值時就進行擴容,這時候上面提到的負載因子就起作用了,所以這裡的newThr為0.75×16 = 12
所以經過這個預設構造方法,並且進行第一次put後,桶陣列建立了,桶容量為16,極限值為12。
(二)呼叫HashMap(int initialCapacity)構造方法
public HashMap(int initialCapacity) {
//這個方法實際呼叫另一個構造方法,所以這個構造方法就不分析了,直接看第三個的分析
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
- 1
- 2
- 3
- 4
(三)呼叫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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
所以經過這個方法之後,桶容量確定了,極限值也確定了,但是桶陣列還是null。
當第一次put時,觸發如下程式碼:
//發現桶陣列是null,所以需要新建一個桶陣列
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
- 1
- 2
- 3
接著看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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
然後還會進入下面這個分支:
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
- 1
- 2
- 3
- 4
- 5
這個分支會按負載因子設定極限值
(四)超過極限值後的擴容
在每次put之後,會有下面這個判斷:
if (++size > threshold)
resize();
- 1
- 2
就是說超過極限值時會進行擴容,擴容方式如下:
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(和負載因子大小有關)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
接上面這個例子,下一次擴容會如何?
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
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
直到…………
當桶容量加倍到最大值會怎麼樣?
//因為每次都是加倍,所以最終肯定會加倍到MAXIMUM_CAPACITY,會進入這個分支。
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
- 1
- 2
- 3
- 4
- 5
桶容量變為最大值時,緊接著的一次擴容只是簡單的將極限值修改為Integer.MAX_VALUE。桶陣列大小並不會繼續加倍
(五)如果HashMap中的鍵值對數量超過Integer.MAX_VALUE了呢?
我們仔細看一下這段程式碼:
//這個size型別是int,那麼最大就是Integer.MAX_VALUE,所以不會有++size > threshold的情況,往後都不會進行擴容了。
if (++size > threshold)
resize();
- 1
- 2
- 3