Jdk1.8集合框架之HashMap原始碼解析(詳細解析紅黑樹)
HashMap特點
- 不同步,支援null的鍵和值,put或get操作通常是常數時間。
- Map介面的實現。
- 去掉了Hashtable的
contains(Object value)
方法,保留containsKey和containsValue方法。 - 使用Iterator而不是Enumration。
對HashMap還不太瞭解的同學,可以先看看這篇文章大致瞭解框架:HashMap簡單入門
內部欄位
// 預設初始長度為16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 桶陣列的最大長度為2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
// 構造器為設定時,預設的負載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 當桶中的節點數大於8時,桶結構由單鏈錶轉化為一個既是紅黑樹又是雙鏈表的結構
static final int TREEIFY_THRESHOLD = 8;
// 當桶中的節點數小於6時,樹轉單鏈表
static final int UNTREEIFY_THRESHOLD = 6;
// 只有桶位陣列大小達到64時,才允許桶位樹化,否則只是擴容
// 至少為4 * TREEIFY_THRESHOLD以避免resize和樹化的衝突
static final int MIN_TREEIFY_CAPACITY = 64;
// 雜湊桶陣列,陣列中儲存的是連結串列或樹節點。長度總是2的整數冪。
transient Node<K,V>[] table;
// HashMap將資料轉換成set的另一種儲存形式,這個變數主要用於迭代功能
transient Set<Map.Entry<K,V>> entrySet;
// Map中的鍵值對個數
transient int size;
// 結構性修改的次數,和迭代器的使用有關,對應fail-fast
transient int modCount;
// 擴容閾值,當table大小超過閾值時要擴容為2倍
int threshold;
// 負載因子,用來計算當前table長度下的容量擴容閾值:threshold = loadFactor * table.length
final float loadFactor;
內部類
a. 單鏈表節點
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // 這個hash值只考慮鍵
final K key; // final:說明節點的key不可變
V value;
Node<K,V> next; // 指向下一個節點,形成單鏈表
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() { // 同時考慮鍵和值
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
b. 樹節點(難點)
這裡的紅黑樹比較特殊,它的規則是:
- 根節點是黑色的。
- 紅節點可以為左孩子,也可以為右孩子,一個節點可以同時有兩個紅色的左右孩子。(有些紅黑樹的實現要求紅節點必須是左孩子)。
- 紅節點的子節點必須是黑色。
- 每個節點到根節點的路徑上黑色節點數量相同。
基本屬性和構造方法
HashMap中的紅黑樹,同時也是一個雙鏈表,parent、left、right和red欄位構建了紅黑樹資訊,而prev、next(next是基類HashMap.Node
的欄位)構建了雙鏈表資訊。樹的root節點同時也是雙鏈表的頭結點,放在桶位陣列上。這種雙結構,使得紅黑樹操作比純粹紅黑樹要複雜一些。
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // 父節點
TreeNode<K,V> left; // 左孩子
TreeNode<K,V> right; // 右孩子
TreeNode<K,V> prev; // 前一個節點
boolean red; // true為紅連結,false為黑連結
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
}
...
}
putTreeVal 方法
HashMap的putVal
方法會呼叫樹方法putTreeVal
來插入新的鍵值對。
該方法首先會查詢樹中是否有這個鍵,有的話就返回這個節點的引用,但注意,這裡沒有馬上更新Value。查詢的過程中,首先比較hash值,其次再比較Key,應該是考慮到比較int型別的速度更快。沒有找到的話,會新建一個樹的葉子節點,再呼叫樹方法balanceInsertion
插入新節點到紅黑樹中。
final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
int h, K k, V v) {
Class<?> kc = null;
boolean searched = false;
// 找到樹根
TreeNode<K,V> root = (parent != null) ? root() : this;
for (TreeNode<K,V> p = root;;) { // p為當前和新元素比較的節點
int dir, ph; K pk;
// 根據hash值比較大小
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
// hash值相等時,如果鍵指向同一地址,查詢結束
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
// hash值相等,比較兩個鍵
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
// 鍵不可比較,或者比較結果為0時,在節點的左右子樹查詢該Key,找到就結束
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
if (((ch = p.left) != null &&
(q = ch.find(h, k, kc)) != null) ||
((ch = p.right) != null &&
(q = ch.find(h, k, kc)) != null))
return q;
}
// 比較兩個物件的記憶體地址
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
// 找到葉子節點還沒找到時
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
// 將當前節點插入到p節點之後,注意這裡的前後是雙鏈表的前後
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
// 當前節點和p節點建立父子關係,這裡是在樹結構中
if (dir <= 0)
xp.left = x;
else
xp.right = x;
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
// 將樹結構中剛剛插入的元素向上紅黑化,然後將樹的根節點排到桶的第一位
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
treeify 方法
HashMap的treeifyBin
方法會呼叫樹方法treeify
,來將雙鏈表構建為紅黑樹。這個方法的主體和putTreeVal
方法類似(不同的是,它不會遇到重複Key值),向只含有一個元素的紅黑樹上不斷新增新的節點。
// 在一個雙鏈表中,將此節點之後的節點構建成紅黑樹結構,返回樹根節點
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
x.left = x.right = null; // 初始化樹節點
// 初始化根節點,根節點為黑連結
if (root == null) {
x.parent = null;
x.red = false;
root = x;
}
// 將每一個節點插入樹中
else {
K k = x.key;
int h = x.hash;
Class<?> kc = null;
for (TreeNode<K,V> p = root;;) {
int dir, ph;
K pk = p.key;
// 根據hash值比較
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
// hash值相等時,比較鍵
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
// 如果節點的鍵不是Comparable類,
// 或者兩個節點鍵比較的結果為相等,就強行讓比較結果不為0
dir = tieBreakOrder(k, pk);
TreeNode<K,V> xp = p;
// 根據比較的結果判斷在左子樹還是右子樹
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 子樹為null,查詢結束,將節點作為樹葉子節點
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 從新新增的元素往上更新紅黑樹結構,見紅黑樹操作
root = balanceInsertion(root, x);
break;
}
}
}
}
// 確保紅黑樹的根節點,同時也是桶位的第一個節點,詳見其他方法
moveRootToFront(tab, root);
}
紅黑樹操作:插入新節點
插入操作主要處理一下三種情況的迭代:
第一種,上浮顏色,上浮後迭代插入紅色的xpp節點(圖中的2)即可。
第二種和第三種通過旋轉轉化成了穩定結構,不需要再迭代。
// 往紅黑樹中平衡插入節點x
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true; // 新節點為紅色
// 注意這是一個迴圈體,將節點一直上浮
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
// x為根節點
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// xp為黑節點,或者xp為根節點時
// 根節點也是黑節點
// 黑節點的孩子可以是紅,所以更新結束
else if (!xp.red || (xpp = xp.parent) == null)
return root;
// 下面的情況中父節點都為紅----------------------------
// xp在xpp的左子樹時
if (xp == (xppl = xpp.left)) {
// xp和xppr都為紅
// 直接上浮顏色即可,見示意圖1,x = xpp繼續迭代
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp; // 上浮節點為xpp
}
// xp是紅,xppr是黑時
// 見示意圖2,最終轉化為父節點為黑,所以下一輪就結束
else {
// 當前節點為右孩子時,左旋父節點
// 轉變為xppl、xppll都為紅的模式
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 轉變為xp的左右孩子都為紅的模式
if (xp != null) {
xp.red = false; // xp改為黑色
if (xpp != null) {
xpp.red = true; // xpp改為紅色
root = rotateRight(root, xpp); // 右旋xpp
}
}
}
}
// xp在xpp的右子樹時
else {
// xp和xppl都為紅
// 直接上浮顏色即可,類似示意圖1,x = xpp繼續迭代
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp; // x上浮到xpp
}
// xp是紅,xppl是黑時
// 見示意圖3,最終轉化為父節點為黑,所以下一輪就結束
else {
// 當前節點為左孩子時,右旋父節點
// 轉變為xppr、xpprr都為紅的模式
if (x == xp.left) {
root = rotateRight(root, x = xp); // 右旋父節點
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 轉變為xp的左右孩子都為紅的模式
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true; // xpp改成紅色
root = rotateLeft(root, xpp); // 左旋xpp
}
}
}
}
}
}
}
紅黑樹操作:左旋
// 節點p左旋轉,注意這裡沒有改變顏色
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
// 當前節點以及右孩子不為空時
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null)
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}
紅黑樹操作:右旋
// 節點p右旋轉,沒有改變顏色
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
// 當前節點以及左孩子不為空時
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}
輔助方法
// 獲得樹的根節點
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
// 確保樹的根節點是桶中的雙鏈表的第一個節點
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
int index = (n - 1) & root.hash; // 找到桶位
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
if (root != first) { // 當根節點不是桶中第一個元素時
Node<K,V> rn;
tab[index] = root; // 根節點放在桶的第一位
TreeNode<K,V> rp = root.prev; // 根的前一個節點
// 將根節點從雙表中抽出,原來的位置前後連結
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
// 根節點放在雙鏈表的首位
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}
// 從this樹節點查詢hash值為h,Key為k的節點
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this;
do {
int ph, dir; K pk;
TreeNode<K,V> pl = p.left, pr = p.right, q; // 當前節點的左右孩子
if ((ph = p.hash) > h) // hash值小的從左子樹迭代查詢
p = pl;
else if (ph < h) // hash值大的從右子樹迭代查詢
p = pr;
// hash值相等,且鍵地址相同或都為空時,查詢成功
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
// hash值相等,但鍵不相同,且節點沒有左子樹,就從右子樹查詢
else if (pl == null)
p = pr;
// hash值相等,但鍵不相同,且節點沒有右子樹,就從左子樹查詢
else if (pr == null)
p = pl;
// 比較兩個Key
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr;
// Key不可比較或比較結果為0時,先在右子樹中查詢
else if ((q = pr.find(h, k, kc)) != null)
return q;
// 右子樹查詢不到時
else
p = pl;
} while (p != null);
return null;
}
// 從根節點查詢hash值為h,Key為k的節點
final TreeNode<K,V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
// 強行比較兩個物件,結果為-1或1
static int tieBreakOrder(Object a, Object b) {
int d;
// a和b都不為空時比較它們的類名
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
// a為null,或b為null,或類名也相等時,比較它們的記憶體地址
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
/**
* Returns a list of non-TreeNodes replacing those linked from
* this node.
*/
final Node<K,V> untreeify(HashMap<K,V> map) {
Node<K,V> hd = null, tl = null;
for (Node<K,V> q = this; q != null; q = q.next) {
Node<K,V> p = map.replacementNode(q, null);
if (tl == null)
hd = p;
else
tl.next = p;
tl = p;
}
return hd;
}
建構函式
HashMap(int initialCapacity, float loadFactor)
選擇不小於intialCapacity的最小的2的冪作為threshold的值。第一次put鍵值對時,初始化table陣列大小為threshold。
HashMap(int initialCapacity)
採用預設的擴容因子0.75f。
HashMap()
採用預設的擴容因子0.75f,threshold為0。第一次put鍵值對時,初始化table陣列大小為16(DEFAULT_INITIAL_CAPACITY)
用集合資料的構造方法,就是將Map中的每個鍵值對新增到
HashMap
中。
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
// 陣列還未初始化時,根據集合中元素數量和負載因子,計算陣列大小的閾值
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
// 陣列已經初始化時,如果Map中元素數量超過閾值,就擴容
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
新增鍵值對:put 方法
採用拉鍊法。插入新的鍵值對,使用hash&table.length
確定桶位,如果桶位為null,直接存放節點。如果不為空,就呼叫這個桶位的的put(K, V)方法。
桶中元素較少時(小於8個),桶為單鏈表,如果插入了一個新的鍵,連結串列長度增加1。當連結串列長度達到8(TREEIFY_THRESHOLD)時,呼叫樹化函式treeifyBin(tab, hash)
。
桶為紅黑樹結構時,呼叫第一個樹節點的putTreeVal
方法。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 陣列未初始化或者長度為0,都要擴容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// Key值對應的桶位若為空,直接新增,皆大歡喜就結束了
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 桶中已經有元素時
else {
Node<K,V> e; K k;
// 新元素如果和第一個元素hash值相等,且Key相等時,直接修改Value
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 否則,如果桶是樹結構,就往樹結構插入新樹節點
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 如果桶是單鏈表結構,就往單鏈表中插入Node節點
else {
for (int binCount = 0; ; ++binCount) {
// 如果已經找到單鏈表的末尾,就將新節點放在末尾,同時判斷是否要樹化
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果新的桶中元素總數達到樹化閾值,判斷是要樹化,還是要擴容
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 如果找到hash值相等,且Key相等的節點,就直接修改Value
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 在上述過程如果不是新增新節點,而是查詢到了Key值相等的舊節點,就返回就舊節點
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; // 在這裡用新值替換舊值
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 元素大小達到閾值時,陣列擴容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
輔助方法:hash 函式
Key在陣列中的桶位,通過(tab.length - 1) & hash
確定。Key為null的時候,hash值為0,桶位索引為0,所以null鍵都被放到table[0]的位置。
static final int hash(Object key) {
int h;
// 可以防止hash陣列太小時,hash值的高位被忽略
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
...
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else
...
}
輔助方法:tableSizeFor 方法
該方法用來將桶位陣列需要的容量擴充到2的自然數冪
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
輔助方法:treeifyBin 函式
HashMap中新增元素時會呼叫putVal
方法,當桶中單鏈表結構元素總數超過8時,呼叫treeifyBin
方法,將桶中的連結串列轉化為紅黑樹,或者只是擴容桶位陣列(會將單鏈表拆分為兩半,達到減小單鏈表長度的目的)。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 桶陣列為空,或者長度小於MIN_TREEIFY_CAPACITY,不符合樹化條件
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); // 擴容
// 符合樹化條件,而且桶位陣列對應位置的桶不為null
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null; // 紅黑樹的頭尾節點
do {
// 把普通節點轉化為樹節點
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p; // 首先確定樹的頭結點
else { // 樹節點同時也是雙向連結串列
p.prev = tl;
tl.next = p;
}
tl = p; // 迴圈的最後確定了樹的尾節點
} while ((e = e.next) != null);
// 將桶中的雙鏈錶轉化成紅黑樹,詳細見樹節點內部方法
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
輔助方法:擴容 resize
- 初始化或者擴容桶位陣列,空陣列的話就根據threshold來計算初始化容量
- 分配到新的散列表時,每個桶位中的元素,要麼不動,要麼後移新陣列長度的一半
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; // 儲存舊的陣列
int oldCap = (oldTab == null) ? 0 : oldTab.length; // 儲存陣列長度
int oldThr = threshold; // 儲存舊的閾值
int newCap, newThr = 0;
// 舊陣列不為null且長度不為0時
if (oldCap > 0) {
// 舊長度達到最大長度限制時,閾值設為最大整型,return
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 否則,擴容一倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
// 擴容後沒超過長度限制&&舊長度>=預設初始長度時,閾值翻倍
newThr = oldThr << 1;
}
// 舊陣列為null,或長度為0時
else if (oldThr > 0) // 舊閾值大於0,就將舊閾值作為新的陣列大小
newCap = oldThr;
else { // 舊閾值為0,舊容量也為0,就採用預設值(使用無參構造器後,插入一個元素就會執行這裡)
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 新閾值為0,新閾值 = 新的長度 * 負載因子
// 但如果新的長度或者計算出來的新閾值超過最大長度限制,就採用最大整型
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 初始化新的桶位陣列
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 舊HashMap中的所有元素分配到新的HashMap
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
// 遍歷處理每一個桶位中的桶
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 只有一個元素的桶,直接重新hash
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 桶中不止一個元素時
else if (e instanceof TreeNode) // 紅黑樹就用split方法分離節點
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // 單鏈表就拆分成兩個連結串列
Node<K,V> loHead = null, loTail = null; // 原索引處的一個連結串列
Node<K,V> hiHead = null, hiTail = null; // 原索引+oldCap的一個連結串列
Node<K,V> next;
do {
next = e.next;
// 根據hash值的某一位為0還是1將單鏈表拆分
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;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}