源碼講解網上太多了,能夠參考HashMap此生來世。 我這裏主要講解Java 8的HashMap擴容原理。下文中聲明的桶與數組的含義是一致的html
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
複製代碼
i = (tab.length - 1) & hash
複製代碼
這與咱們平時學的hash表算法不太同樣,先說效果:位運算計算快,效率高,與數組長度取模(好比Hashtabal的put方法就是哈希取模)效果同樣。 由於桶的個數是2的n次冪,以默認16舉例。tab.length -1 =15,二進制就是 1111,與hash進行&運算,獲得的結果是hash值的最後4位。運算規則參考我上一篇文章算法
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
// oldCap = 16
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;
}
// newCap =oldCap << 1 = 32
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
···// 省略其餘代碼,下面就是擴容代碼,數據怎麼轉移到新數組newTab中
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// oldCap是舊桶的個數,也就是沒擴容以前數組的長度,這裏等於16 。這裏遍歷桶
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null; //清除桶元素,利於回收
// 若是桶中元素後面沒有其餘元素,直接計算新index,插入新桶中
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 若是桶中元素還有下一個元素,節點類型是TreeNode類型,那麼按照TreeNode方式插入。這個比較複雜
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
// 到了這個分支,就說明節點類型是鏈表,咱們主要分析這個
else {
// preserve order
// 3.1
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
// j + oldCap是由於高位,正好是2的4次方,也就是oldCap的長度
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
複製代碼
這裏註釋preserve order說明了下面鏈表的遷移保持順序,讓我想起來Java 1.7的HashMap在擴容的時候,全部元素所有從新計算Hash&(NewTable.length-1),致使擴容後的鏈表沒有保持順序,因此在併發的Rehash狀況下會致使死循環。shell
來看看核心判斷條件(e.hash & oldCap) == 0。這裏oldCap=16,就是Hash值與10000進行或運算(只有兩個數都爲1,結果才爲1)。計算結果也就是判斷Hash值的第5個bit位是否等於0。這裏爲何要判斷第5個bit位呢?由於以前計算桶下標的時候是直接取的hash值的前4位,若是擴容後,計算t桶下標方式就是(hash & (newCap - 1)) ,newCap =32,也就是hash & (100000-1),也就是與11111進行或運算,獲得的結果就是hash值的前5位。看到這裏不知道你是否有點迷糊,那就看個圖。數組
看到這裏,你會發現這個設計很是的巧妙,避免了所有從新Rehash的過程,而且新增的bit爲能夠認爲是隨機的,這樣子就能夠將以前衝突的數據分散到新的桶中。bash
參考部分:併發
HashMap今世前身ide