個人全部文章同步更新與Github--Java-Notes,想了解JVM,HashMap源碼分析,spring相關,劍指offer題解(Java版),能夠點個star。能夠看個人github主頁,天天都在更新喲。java
邀請您跟我一同完成 repogit
首先你應當記住的:無論你傳不傳參數,無論你傳入的長度爲多少,在你用HashMap的時候,他的長度都是2的n次方,且最大長度爲2的30次方github
在HashMap的源碼中,最大長度這個常量值是這樣定義的算法
/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */
static final int MAXIMUM_CAPACITY = 1 << 30;
複製代碼
這個值用在哪裏呢?spring
注意,源碼中他們採用了延遲初始化操做,也就是table只有在用到的時候才初始化,若是你不對他進行put
等操做的話,table的長度永遠爲"零"數組
主要有兩個函數保證了他的長度爲2的n次方函數
至於計算過程以及加載過程,請參考個人這篇文章:table的長度究竟是多少源碼分析
這篇文章我從源碼分析table的建立過程,包括上面提到的函數的調用,看了這個你必定明白爲啥table
的長度必定是2的n次方spa
固然我針對hashMap寫的一部分源碼的中文註釋github上也有:HashMap源碼中文註釋.net
若是不是2的n次方,那麼有些位置上是永遠不會被用到
具體能夠參考這篇博文,他用例子講述了爲何,爲啥長度要是2的n次方
當容量必定是2^n時,h & (length - 1) == h % length
擴容後計算新位置,很是方便,相比 JDK1.7
在 JDK1.8 中,HashMap有了挺大的改動,包括
元素遷移算法(舊的到新的數組)
使用紅黑樹
鏈表爲尾插法
其中我重點講下元素遷移算法,JDK1.8的時候
首先看下java代碼以及個人註釋,若是要看完整的,能夠看個人github倉庫
// 將原來數組中的全部元素都 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;
}
}
}
}
}
複製代碼
咱們看到上面源碼的最後一句,
newTable[j+oldCap] = hiHead;
意思就是哪怕咱們的元素從舊的數組遷移到新的數組,咱們也不須要從新計算他的hash和新數組長度相與的值,只須要直接將如今的索引值+原來數組的長度
便可
藍色的表示不須要移動的,綠色的表示須要從新計算索引的,咱們看到,他只是加了16(原來的數組table長度)
咱們注意到上面的源代碼中,判斷擴容後元素位置需不須要改變的時候,咱們使用到了這個判斷
if((e.h & oldCap) == 0)
,
若是爲0,那麼就不須要改變,使用舊的索引便可;若是爲1,那麼就須要使用新的索引
爲啥會這樣呢?
hash&(newTable.length-1)
必定是和 hash&(oldTable.length-1)+oldTable.length
相等咱們來舉個例子:
咱們假設元素的hash值的後12位是 110111010111,數組原來的長度爲16,擴容後數組長度爲32
你能夠試下下次擴容時,擴容到64時,索引變不變化。固然答案是不會變化,由於元素的hash值在那個位置爲 0
咱們來對比JDK1.7 的方式,他若是要擴容,而且擴容後計算元素的索引的話要使用 indexFor函數
/** * Returns index for hash code h. */
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
複製代碼
也就是要把元素的hash值從新再和新的數組長度-1 再相與一次,會比較麻煩並且不優雅,徹底沒有我看到1.8計算方式的那種驚豔感。