個人全部文章同步更新與Github--Java-Notes,想了解JVM,HashMap源碼分析,spring相關,劍指offer題解(Java版),能夠點個star。能夠看個人github主頁,天天都在更新喲。html
邀請您跟我一同完成 repojava
記住一點,當table進行初始化的時候,table.length 就是 比傳入的值大的或者等於的最小的 2的n次方
,table.length 的長度一直是 2的n次方git
也就是說,我new HashMap(0)
,table初始化後 table.length ==1(固然,源碼中全部的變量都採用延遲初始化,只有等到用的時候,即put元素的時候才初始化。若是沒有放入元素,那麼 table一直爲 null,我上面的只是另一個變量 threshold
得出來的,由於這兩個有關係。我使用斷點調試一下就知道了)github
咱們注意到,當構建函數時,threshold的初始值和 tableSizeFor()
這個函數有關spring
tableSizeFor
這個是返回一個比輸入值大的或者等於的最小的 2的n次方(若是你不明白能夠看下我後面的測試值和這篇文章數組
www.cnblogs.com/loading4/p/…函數
因此他返回的值爲 1(比0大的或者等於的最小的2的n次方 就是1);源碼分析
而後這個時候假如咱們第一次 put一個元素,這個時候table數組就要開始初始化了,他就會執行這個resize
函數,在源碼中是這樣測試
而resize
的解讀以下spa
/** * 擴容函數 * 使用情景: 1.初始化數組table, * 2.++size>threshold. * @return 新的數組table * 或者是當擴容一倍長度超過最大值,返回原來的table數組 */
final Entry[] resize() {
// 定義舊的數組爲 Entry 類型的數組,oldTab
Entry[] oldTab = table;
// 若是oldTab==null 則返回 0,不然返回數組大小
int oldCap = (oldTab==null) ? 0 : oldTab.length;
int oldThreshold = threshold;
int newCap,newThreshold=0;
// 說明已經不是第一次 擴容,那麼已經初始化過,容量必定是 2的n次方,因此能夠直接位運算
if(oldCap>0){
// 若是 原來的數組大小已經大於等於了最大值,那麼閾值設置爲 Integer的最大值,即不會再進行擴容
if(oldCap >= MAX_CAPACITY){
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 所以已經不是第一次擴容,必定是2的n次方
else if ((newCap = oldCap << 1) < MAX_CAPACITY &&
oldCap >= INIT_CAPACITY)
newThreshold = oldThreshold << 1;
}
// 若是oldThreshold > 0,而且oldCap == 0,說明是尚未進行調用resize方法。
// 說明輸入了初始值,且oldThreshold爲 比輸入值大的最小的2的n次方
// 那麼就把 oldThreshold 的值賦給 newCap ,由於這個值如今爲 比輸入值大的最小的2的n次方
else if(oldThreshold>0)
newCap = oldThreshold;
// 這個是隻有使用無參構造器的時候才能知足的條件。,所有是否默認的值
else{
newCap = INIT_CAPACITY;
newThreshold = (int) (INIT_CAPACITY * DEFAULT_LOADFACTOR);
}
//
if(newThreshold == 0){
float ft = (float) (newCap * loadFactor);
newThreshold =(newCap < MAX_CAPACITY && ft < (float) MAX_CAPACITY ?
(int )ft : Integer.MAX_VALUE);
}
threshold = newThreshold;
Entry newTable[] = new Entry[newCap];
table=newTable;
// 將原來數組中的全部元素都 copy進新的數組
if(oldTab != null){
for (int j = 0; j < oldCap; j++) {
Entry e;
if((e = oldTab[j]) != null){
oldTab[j] = null;
// 說明尚未成鏈,數組上只有一個
if(e.next == null){
// 從新計算 數組索引 值
newTable[e.h & (newCap-1)] = e;
}
// 判斷是否爲樹結構
//else if (e instanceof TreeNode)
// 若是不是樹,只是鏈表,即長度尚未大於 8 進化成樹
else{
// 擴容後,若是元素的 index 仍是原來的。就使用這個lo前綴的
Entry loHead=null, loTail =null;
// 擴容後 元素index改變,那麼就使用 hi前綴開頭的
Entry hiHead = null, hiTail = null;
Entry next;
do {
next = e.next;
//這個很是重要,也比較難懂,
// 將它和原來的長度進行相與,就是判斷他的原來的hash的上一個 bit 位是否爲 1。
//以此來判斷他是在相同的索引仍是table長度加上原來的索引
if((e.h & oldCap) == 0){
// 若是 loTail == null ,說明這個 位置上是第一次添加,沒有哈希衝突
if(loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else{
if(hiTail == null)
loHead = e;
else
hiTail.next = e;
hiTail = e ;
}
}while ((e = next) != null);
if(loTail != null){
loTail.next = null;
newTable[j] = loHead;
}
// 新的index 等於原來的 index+oldCap
else {
hiTail.next = null;
newTable[j+oldCap] = hiHead;
}
}
}
}
}
return newTable;
}
複製代碼
咱們注意這裏,知足這個的條件爲 oldCap==0 && oldThreshold>0
else if(oldThreshold>0)
newCap = oldThreshold;
複製代碼
oldThreshold
等於以前使用 tableSizeFor 的返回值,也就是 1;因此table
一經初始化長度就爲1**(可是還要說明一點,執行完 resize函數以後 table.length等於1,threshold等於0。看我上面的分析,newThreshold=(int)0.75)就等於0**
可是咱們執行的是存入元素的操做,因此存完以後,就要++size(由於以前裏面沒有元素),這個時候 size就等於1,他就大於 threshold。因此他又要執行resize
函數進行擴容操做。執行完以後,table.length 就等於2了,threshold就等於 1了。
table的擴容所有都是乘以 2(左移一位),並且table.length也一直等於2的n次方,即(table.length &(table.length-1)) == 0
以後同理能夠推,一開始建立對象時,傳入 1的話,table 初始化長度爲1;傳入 2,長度爲2;傳入3,長度爲4;傳入[5,8],長度爲8;傳入[9,16],長度爲16