早安,打工人.现在只要去面试无论大厂小厂几乎都包含了HashMap,咱们就来看看HashMap源码.上次我们说到了Hashtable是数组+链表结构,而HashMap则是数组+链表+红黑树结构.
咱们在正式开讲之前需要先知道什么是红黑树
概念
红黑树(R-B Tree),全称Red-Black Tree
是一种自平衡二叉查找树,可以在==O(logN)==时间内完成查找,增加,删除等操作.它的每个节点上都存储了颜色标识,可以是红或黑.在进行插入和删除等可能破坏树平衡的操作时,会通过变色,左旋,右旋来完成自平衡.
红黑树的特性:
- 每个节点非黑即红
- 根节点是黑色
- 所有叶子节点都是黑色(NIL节点)
- 每个红色节点的两个子节点都是黑色
- 从任一节点到其叶子节点的所有路径都包含相同数量的黑色节点
这些特性确定了红黑树的关键性质: 从根到叶子的最长路径不会多于最短路径的两倍长,从而这个树大致上是平衡的.因为插入,删除和查找某个值的最坏情况实际都要求与树的高度成比例,这个高度上的理论上线允许红黑树在最坏的情况下都是高效的,而不同于普通的二叉查找树.
如图即是一个简单的红黑树,其中NIL
是叶子节点且是黑色的.红黑树不是一个完美平衡二叉树,但从任一节点到其叶子节点的路径中包含数量相同的黑色节点(即左子树和右子树上的黑色结点层数是相等的),所以我们称红黑树的这种平衡为黑色完美平衡
.
上面有讲到红黑树的自平衡的三种操作: 变色
左旋
右旋
.
-
变色
- 节点颜色由红变黑,或由黑变红
-
左旋
- 以某节点为支点,其右子节点变为支点的父节点,右子节点的左节点变为支点的右节点,支点变为右子节点的左节点.
-
右旋
- 以某节点为支点,其左子节点变为支点的父节点,左子节点的右子节点变为支点的左子节点,支点变为左子节点的右节点.
以上为红黑树的大概介绍,听着比较迷糊,下面将结合HashMap源码详细讲解红黑树的各种操作.
TreeNode解析
在HashMap中针对红黑树的操作主要是通过TreeNode来进行的
static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V> {
// 父节点
TreeNode<K, V> parent; // red-black tree links
// 左节点
TreeNode<K, V> left;
// 右节点
TreeNode<K, V> right;
// 上一个节点
TreeNode<K, V> prev; // needed to unlink next upon deletion
// 是否为红色
boolean red;
TreeNode(int hash, K key, V val, Node<K, V> next) {
super(hash, key, val, next);
}
TreeNode继承自LinkedHashMap.Entry
,而LinkedHashMap.Entry
又继承自Node
,TreeNode中保留了Node里的属性,同时增加了prev
字段使链表变成了双向的.
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
下边来看看TreeNode的函数
rotateLeft(TreeNode<K, V> root,TreeNode<K, V> p)
左旋,逆时针旋转支点,使支点的位置被其右子节点取代,而支点本身则变为其右子节点的左节点.
// 左旋
static <K, V> TreeNode<K, V> rotateLeft(TreeNode<K, V> root,
TreeNode<K, V> p) {
TreeNode<K, V> r, pp, rl;
// p为旋转支点
// 如果p的右节点不为空则进行左旋
if (p != null && (r = p.right) != null) {
// p的右节点的左节点顶替p的右节点位置
if ((rl = p.right = r.left) != null)
rl.parent = p;
// r替代p的位置, 如果p的父节点为空,说明是根节点,设置为黑色
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
// 用 r 替换 原来的 p
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
// 再把原来的p 放到 r 的left属性
r.left = p;
p.parent = r;
}
return root;
}
rotateRight(TreeNode<K, V> root, TreeNode<K, V> 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;
}
putTreeVal(HashMap<K, V> map, Node<K, V>[] tab,int h, K k, V v)
红黑树的插入包含两部分操作,一是查找插入的位置,二是节点插入后,红黑树的自平衡.
查找插入的位置比较简单,就是由根节点向下迭代,找寻合适的左右枝一直往下到叶子节点,插入后会进行第二步自平衡.
// 插入一个节点/
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; ; ) {
int dir, ph;
K pk;
// p的hash 大于 要插入的 ,放左边
if ((ph = p.hash) > h)
dir = -1;
// 放右边
else if (ph < h)
dir = 1;
// 若是key相同
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
// comparable比较pk和k的大小,如若不能比较或比较不出
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
// 向下找一次,确定匹配
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;
}
// 针对hash相同但不能进行comparable比较的统一规则处理,dir要么-1 要么1
dir = tieBreakOrder(k, pk);
}
TreeNode<K, V> xp = p;
// 如果插入节点所属左右节点不是是叶子就继续往下层迭代
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 维护next,prev,便于树和链表转化
Node<K, V> xpn = xp.next;
TreeNode<K, V> x = map.newTreeNode(h, k, v, xpn);
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;
// 插入元素后做自平衡调整,同时确保树根root为链表头元素
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
由插入节点自下而上遍历,根据父节点,叔叔节点和爷爷节点的位置和颜色来确定是否需要变色和旋转,自平衡分为四种情况:
- 当前节点是根节点
- 设置当前节点为黑色
- 父节点为黑色或爷爷节点为黑色
- 无需变色
- 父节点为红色且叔叔节点是红色
- 设置父节点和叔叔节点为黑色,爷爷节点为红色
- 父节点为红色且叔叔是null或黑色
- 设置父节点为黑色,爷爷节点为红色,再进行旋转重订位置
// 此函数完成整个红黑树位置的调整和颜色的变化
static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root, TreeNode<K, V> x) {
// root 根节点, x为迭代节点
// 默认设置叶子节点为红色
x.red = true;
for (TreeNode<K, V> xp, xpp, xppl, xppr; ; ) {
// 1.如果x没有parent,说明是根节点,则设为黑色,并返回
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
// 2.如果x的父节点是黑色或x的爷爷是根节点 正常返回
else if (!xp.red || (xpp = xp.parent) == null)
// 返回 原来的根节点
return root;
// 3.父节点为红色且是父节点为爷爷的左节点
if (xp == (xppl = xpp.left)) {
// 3.1. 叔叔节点为红色
if ((xppr = xpp.right) != null && xppr.red) {
// 设置父节点和叔叔节点为 黑色,爷爷节点为红色
xppr.red = false;
xp.red = false;
xpp.red = true;
// 再赋值当前迭代节点为爷爷节点,向上迭代平衡树
x = xpp;
} else {
// 3.2.叔叔节点为null或黑色
// 当前节点和父节点,爷爷节点不是一条直线,则先左旋调整
// 如果当前节点是 父 节点的 right节点
if (x == xp.right) {
// 左旋,以父节点为支点
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 2. 再右旋
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp);
}
}
}
} else {
// 走到这里 说明 父节点是爷爷节点的右节点
// 操作和上面的相反
if (xppl != null && xppl.red) {
xppl.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
} else {
if (x == xp.left) {
root = rotateRight(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
root()
// 获取当前节点所属数的根节点
final TreeNode<K, V> root() {
// 向上父遍历
for (TreeNode<K, V> r = this, p; ; ) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
moveRootToFront(Node<K, V>[] tab, TreeNode<K, V> root)
// 确保给定根节点root是红黑树的根节点
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) {
// root的next指向原来的根节点,原来的根节点的prev指向root
// root的next的prev指向root的prev,root的prev指向指向root的next
// 即把root移除原位置并顶替头
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);
}
}
// 递归检测红黑树不变性
static <K, V> boolean checkInvariants(TreeNode<K, V> t) {
TreeNode<K, V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode<K, V>) t.next;
if (tb != null && tb.next != t)
return false;
if (tn != null && tn.prev != t)
return false;
if (tp != null && t != tp.left && t != tp.right)
return false;
if (tl != null && (tl.parent != t || tl.hash > t.hash))
return false;
if (tr != null && (tr.parent != t || tr.hash < t.hash))
return false;
if (t.red && tl != null && tl.red && tr != null && tr.red)
return false;
if (tl != null && !checkInvariants(tl))
return false;
if (tr != null && !checkInvariants(tr))
return false;
return true;
}
find(int h, Object k, Class<?> kc)
查找元素也比较简单
- 以当前节点为跟根节点开始查找
- 若当前节点为空,则返回null
- 若当前节点不为空,用当前节点的hash和key根要查找的key作比较
- 根据比较结果选择左枝或右枝继续向下层迭代
// 以当前节点为根节点向下 查找 指定hash和key,class的元素
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) // 1.在left节点下
p = pl;
else if (ph < h) // 2.在right节点下
p = pr;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
// 3.hash和根节点相同,且k也和根节点k相同
return p;
// 执行到这里说明hash相同,但当前迭代的pk和要查找元素的k不同
else if (pl == null) // 4.如果左节点为空,p = right节点,进行下轮迭代
p = pr;
else if (pr == null) // 5.如果right节点为null,p = left节点,进行下轮迭代
p = pl;
else if ((kc != null ||
(kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
// 6.这里说明左右节点都不为空,则进行comparable比较pk和k的大小
p = (dir < 0) ? pl : pr;
else if ((q = pr.find(h, k, kc)) != null)
// 7.这里说明无法comparable比较 或 比较后还是相等,则递归从右子节点向下找,如果匹配到了直接返回
return q;
else
// 8.若右子节点找不到,则从左子节点开始下轮迭代
p = pl;
} while (p != null);
return null;
}
getTreeNode(int h, Object k)
// 从根节点开始查找指定元素
final TreeNode<K, V> getTreeNode(int h, Object k) {
return ((parent != null) ? root() : this).find(h, k, null);
}
tieBreakOrder(Object a, Object b)
// 确保在向红黑树插入hashCode相同元素的时候,插入顺序一致的
// 插入顺序的规则指定
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
// 对比两个对象内存地址hashCode, 小或等于 都是 -1
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
void treeify(Node<K, V>[] tab)
此函数实现链表转红黑树结构,实际上维护了两层关系, 通过next
和prev
属性来维护链表,parent
,left
,right
和red
来维护红黑树.
// 链表转为红黑树结构
final void treeify(Node<K, V>[] tab) {
TreeNode<K, V> root = null;
// 遍历这个 TreeNode的 链表
for (TreeNode<K, V> x = this, next; x != null; x = next) {
next = (TreeNode<K, V>) x.next;
x.left = x.right = null;
// 如果父节点为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;
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
TreeNode<K, V> xp = p;
// dir <=0 计算该元素是在节点的left上还是在right上 (小的在left上,大的在right上)
if ((p = (dir <= 0) ? p.left : p.right) == null) {
// 若left或right为null,则创建
// 设置当前节点的父类为根节点
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
// 调整平衡
root = balanceInsertion(root, x);
break;
}
}
}
}
// 这里确保 这个红黑树的根节点root 是 链表的第一个元素
moveRootToFront(tab, root);
}
untreeify(HashMap<K, V> map)
红黑树退化为链表,通过维护的next属性完成
// 树转链表
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;
}
removeTreeNode(HashMap<K, V> map, Node<K, V>[] tab,boolean movable)
删除和插入一样有两个步骤,第一查找需要删除的元素,第二删除后自平衡.第一步就不用说了,和查找操作一样.
当不存在目标节点时忽略本次操作,若存在,删除后就需要做自平衡了,删除节点时我们还需要找一个替换节点来替代删除节点的位置,不然删除后就节点树就断开了,如果删除节点没有子节点则不需要替换.
后继节点: 右子树最左子节点.
// 删除指定节点
final void removeTreeNode(HashMap<K, V> map, Node<K, V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0)
return;
int index = (n - 1) & hash;
TreeNode<K, V> first = (TreeNode<K, V>) tab[index], root = first, rl;
TreeNode<K, V> succ = (TreeNode<K, V>) next, pred = prev;
// 维护链表关系
// 被删除的是根节点,则用下一个节点替代
if (pred == null)
tab[index] = first = succ;
else
// 上一个节点不为空,则将pred.next 指向 当前节点.next (即删除当前节点)
pred.next = succ;
if (succ != null)
succ.prev = pred;
if (first == null)
return;
// 根节点存在父节点,说明不是根节点,调用root()确保是根节点
if (root.parent != null)
root = root.root();
// 根据根节点的左右枝判断红黑树上节点的数量从而转换为链表
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map); // too small
return;
}
// p为要删除节点 replacement 为删除后用于替代的节点(从左右枝中"选拔")
TreeNode<K, V> p = this, pl = left, pr = right, replacement;
if (pl != null && pr != null) {
TreeNode<K, V> s = pr, sl;
// 1. 若删除节点有两个子节点,用后继节点(即大于删除节点最小节点)替代删除节点
while ((sl = s.left) != null) // find successor
s = sl;
boolean c = s.red;
s.red = p.red;
p.red = c; // swap colors
TreeNode<K, V> sr = s.right;
TreeNode<K, V> pp = p.parent;
// 如果 后继节点就是 删除节点右节点,直接替换
if (s == pr) { // p was s's direct parent
p.parent = s;
s.right = p;
} else {
// 把删除元素 顶替 后继节点
TreeNode<K, V> sp = s.parent;
if ((p.parent = sp) != null) {
if (s == sp.left)
sp.left = p;
else
sp.right = p;
}
if ((s.right = pr) != null)
pr.parent = s;
}
p.left = null;
// 最小右子节点肯定没有左子节点,但不一定没有右子节点
if ((p.right = sr) != null)
sr.parent = p; // 赋值右子节点
if ((s.left = pl) != null) // 删除节点的左枝 赋值给 后继节点
pl.parent = s;
// 把后继节点 指向 删除节点父节点的子节点
if ((s.parent = pp) == null)
root = s;
else if (p == pp.left)
pp.left = s;
else
pp.right = s;
// sr不为空,replacement为 后继节点的右节点
if (sr != null)
replacement = sr;
else
// 否则为删除节点
replacement = p;
// 2.删除节点只有一个子节点,用子节点替换删除节点
} else if (pl != null)
replacement = pl;
else if (pr != null)
replacement = pr;
else
// 3.若删除节点不存在子节点,则直接删除
replacement = p;
// replacement 要么为替代节点的子节点要么就是删除节点
// replacement!=p的情况
// p有子节点,或 p有后继节点
if (replacement != p) {
// 相当于把删除节点移到树末端,来进行删除
TreeNode<K, V> pp = replacement.parent = p.parent;
if (pp == null)
root = replacement;
else if (p == pp.left)
pp.left = replacement;
else
pp.right = replacement;
// 清理p
p.left = p.right = p.parent = null;
}
// p 若为红色删除无影响, 若为黑色则需要平衡
TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement);
// p没有左右节点 或 p的最小右子节点没有右节点,直接移除p
if (replacement == p) { // detach
TreeNode<K, V> pp = p.parent;
p.parent = null;
if (pp != null) {
if (p == pp.left)
pp.left = null;
else if (p == pp.right)
pp.right = null;
}
}
// 处理根节点
if (movable)
moveRootToFront(tab, r);
}
以上图为例演示
删除后自平衡比较复杂有多种情况
- 替换节点是根或红色
- 直接把替换节点设置为黑色
- 替换节点是黑色,且替换节点的兄弟节点是红色
- 将兄弟节点设为黑,父节点设为红,旋转父节点,将情况转化
- 替换节点是黑色,且兄弟节点和兄弟的子节点均为黑色
- 将兄弟节点设为红色,将父节进行旋转调色,转化情况
- 替换节点是黑色,且兄弟节点是黑色,兄弟节点的右节点是黑色
- 兄弟节点的左节点设为黑色,兄弟节点设为红色,以兄弟节点为支点旋转,转化情况
- 替换节点是黑色,且兄弟节点是黑色,兄弟节点的右节点是红色
- 兄弟节点颜色设置成父节点的颜色,兄弟节点的右节点设置成黑色,父节点设置成黑色,以父节点为支点旋转
// 删除的节点为黑色则需要平衡
static <K, V> TreeNode<K, V> balanceDeletion(TreeNode<K, V> root,
TreeNode<K, V> x) {
for (TreeNode<K, V> xp, xpl, xpr; ; ) {
if (x == null || x == root)
return root;
else if ((xp = x.parent) == null) {
x.red = false;
return x;
} else if (x.red) {
// 1. x为红色
//处理 变为黑色
x.red = false;
return root;
} else if ((xpl = xp.left) == x) {
// 2. x为黑色,且兄弟节点是红色
// 处理: 兄弟节点变黑,父节点变红,以父节点为支点进行左旋
if ((xpr = xp.right) != null && xpr.red) {
xpr.red = false;
xp.red = true;
root = rotateLeft(root, xp);
// 旋转后位置变了,重新赋值,继续参与自平衡
xpr = (xp = x.parent) == null ? null : xp.right;
}
// 不存在兄弟节点,则指向父节点向上处理
if (xpr == null)
x = xp;
else {
// 3.x兄弟为黑色,且兄弟的左右子节点均为黑色
// 处理: 兄弟节点变红,指向父节点继续自平衡
TreeNode<K, V> sl = xpr.left, sr = xpr.right;
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
// 把兄弟设为红
xpr.red = true;
// x指向xp进行上溯
x = xp;
} else {
// 4.兄弟的右节点是黑色,左节点是红色
// 处理: 左节点设为黑色,兄弟节点设为红色,以兄弟节点为支点右旋,继续自平衡
if (sr == null || !sr.red) {
if (sl != null)
sl.red = false;
xpr.red = true;
root = rotateRight(root, xpr);
xpr = (xp = x.parent) == null ?
null : xp.right;
}
if (xpr != null) {
// 5.兄弟节点的右节点是红色
// 处理: 1.兄弟节点颜色设置成父节点的颜色
// 2.右孩子设置成黑色
xpr.red = (xp == null) ? false : xp.red;
if ((sr = xpr.right) != null)
sr.red = false;
}
// 父节点存在设置为黑色
if (xp != null) {
xp.red = false;
// 以父节点进行左旋
root = rotateLeft(root, xp);
}
// 替换节点指向根节点,平衡结束
x = root;
}
}
} else { // 和上述对称
if (xpl != null && xpl.red) {
xpl.red = false;
xp.red = true;
root = rotateRight(root, xp);
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl == null)
x = xp;
else {
TreeNode<K, V> sl = xpl.left, sr = xpl.right;
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
xpl.red = true;
x = xp;
} else {
if (sl == null || !sl.red) {
if (sr != null)
sr.red = false;
xpl.red = true;
root = rotateLeft(root, xpl);
xpl = (xp = x.parent) == null ?
null : xp.left;
}
if (xpl != null) {
xpl.red = (xp == null) ? false : xp.red;
if ((sl = xpl.left) != null)
sl.red = false;
}
if (xp != null) {
xp.red = false;
root = rotateRight(root, xp);
}
x = root;
}
}
}
}
}
split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit)
split
函数是将旧数组转移到新数组,分为两个步骤.
- 迭代链表,根据
e.hash & bit == 0
重新计算它在树链表的位置, 如果e.hash & bit == 0
记入低位区树链表lo. 若e.hash & bit != 0
,记入高位区树链表hi. - 根据低位区和高位区元素的数量来进行树的转换或去树化,同时低位区元素位置不变,高位区元素会放在新数组中的新位置上
tab[index + bit]
// 这个函数是对树的拆分,构成 会在HashMap rehash的时候调用
final void split(HashMap<K, V> map, Node<K, V>[] tab, int index, int bit) {
TreeNode<K, V> b = this;
// Relink into lo and hi lists, preserving order
TreeNode<K, V> loHead = null, loTail = null;
TreeNode<K, V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
for (TreeNode<K, V> e = b, next; e != null; e = next) {
next = (TreeNode<K, V>) e.next;
e.next = null;
// bit代表扩容的二进制位(数值为扩容前的容量大小)
// 0 说明是在低位树,原则位置
if ((e.hash & bit) == 0) {
// 如果当前节点没有尾节点,就设置为头节点
if ((e.prev = loTail) == null)
loHead = e;
else
// 把当前节点设置上一个尾节点的下一个节点
loTail.next = e;
// 设置尾节点
loTail = e;
++lc;
} else {
// 高位树上的处理
// 这里把原来的尾节点赋值给e.prev,同时判断是否尾节点为null,说明没有尾节点,再设置e为头节点
if ((e.prev = hiTail) == null)
hiHead = e;
else
// 说明有尾节点,把尾节点的下一个设置成e
hiTail.next = e;
// 同时 把 e 设置成新的尾节点
hiTail = e;
++hc;
}
}
// 对高低位树进行处理,将数组节点指向树根节点或者链表首节点
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
// 拆分之后节点小于非树化阈值,转成链表结构
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
// 高位树为空则 表示节点全部留在了低位树,不需要进行树化操作,已经树化过了
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
// 高位所处的位置为原本位置+旧数组的大小即bit
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
以上为HashMap中TreeNode红黑树部分的相关介绍,下篇文章我们会详细介绍HashMap其余Api和实现原理.