- 这篇文章主要关注HashMap中有关红黑树的相关操作。
内部红黑树结点类
属性
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; //跟next属性相反的指向 删除后需要取消next的链接
boolean red; //结点颜色
}
复制代码
- TreeNode 继承了 LinkiedHashMap.Entry,LinkedHashMap.Entry继承了 HashMap.Entry。
内部方法
左旋
//左旋
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
//变量的命名非常讲究 : r:p的right节点,pp:p的parent节点,rl:p的右孩子的左孩子节点
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) {
// “=”是右结合的。rl=p.right=r.left 相当于 rl=(p.right=r.left)
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;
}
复制代码
右旋
//右旋
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;
}
复制代码
插入后平衡操作
- 红黑树在插入一个结点后,需要保持红黑树的属性,需要进行平衡操作。
/**
* 插入结点的平衡操作
* @param root
* @param x
* @param <K>
* @param <V>
* @return
*/
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没有父结点,说明x是根结点,设置x的颜色是黑色,然后返回x,如图1
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//如果父结点是黑色的,或者父结点的父结点是null(父结点是根结点),直接返回根结点,如图2
else if (!xp.red || (xpp = xp.parent) == null)
return root;
//x结点在父结点的父结点的左子树上时
if (xp == (xppl = xpp.left)) {
// xpp
// / \
// xp(xppl) xppr
// /
// x
//xp有兄弟结点且兄弟结点也是红色,进行颜色翻转操作,如图3
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else {
//如图4中的(1)
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
//如图4中的(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);
}
}
}
}
}
}
复制代码
树的分裂
- JDK1.8 中为了提高性能,当链表长度大于等于树化阈值8时,链表结构会转换为红黑数结构存储冲突元素,在扩容时要进行树的分裂操作,如果当前索引中元素结构是红黑树且元素个数小于等于链表还原阈值6时,就会还原为链表结构。
/**
* 红黑树分裂。
* 在扩容时被调用,一棵红黑树分裂成两棵。
* 如果分裂后的树的结点值小于等于UNTREEIFY_THRESHOLD,就将分裂后的这棵树转换为链表。
*/
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
//当前这个结点的引用,即这个索引树上的根结点
TreeNode<K,V> b = this;
//重新将结点连接到lo或hi,保持顺序
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
//高位地位的树初始结点个数都是0
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;
//结点的hash值和旧容量相与,如果是0,连接在低位树lo,如果是1,连接在高位树hi
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
if (loHead != null) {
//如果数量小于等于 UNTREEIFY_THRESHOLD,将树转换成链表
if (lc <= UNTREEIFY_THRESHOLD)
//低位树放在index位置
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
//如果hiHead==null,说明loHead是原来的树,已经树形化了,就不用再树形化,否在要对loHead进行树形化
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
//高位树是放在(index+原来容量)的位置
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
复制代码
将链表转换为树
/**
* 将链表结点转换为树结点
* @param tab
*/
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
//从this结点开始,this结点是桶中的第一个结点
for (TreeNode<K,V> x = this, next; x != null; x = next) {
next = (TreeNode<K,V>)x.next;
//先对x结点初始化
x.left = x.right = null;
//如果根结点是null,那么x结点就是根结点
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);
//上面判断了下一次要遍历的位置,即dir
TreeNode<K,V> xp = p;
if ((p = (dir <= 0) ? p.left : p.right) == null) {
x.parent = xp;
if (dir <= 0)
xp.left = x;
else
xp.right = x;
//插入结点后进行平衡操作
root = balanceInsertion(root, x);
break;
}
}
}
}
//确保红黑树的根节点是桶中第一个结点
moveRootToFront(tab, root);
}
复制代码
将树转换为链表
- 因为可能需要重新将树转换为链表,所以在对红黑树进行插入等操作时,我们需要维护树结点的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;
}
复制代码
红黑树插入
- 插入和删除是红黑树的核心操作,都是以上面的左旋和右旋操作为基础进行操作的。
/**
* 红黑树的插入操作
*/
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;
//HashMap的红黑树默认情况下根据结点的key的hash值排序,先比较当前结点的hash值和要插入结点的hash值的大小。如果大于,令dir=-1
if ((ph = p.hash) > h)
dir = -1;
//如果小于,令dir=1
else if (ph < h)
dir = 1;
//要插入的结点已经存在,返回存在的结点
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
return p;
//如果当前插入的类型和正在比较的节点的Key是Comparable的话,就直接通过此接口比较
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0) {
if (!searched) {
TreeNode<K,V> q, ch;
searched = true;
//尝试在p的左子树或者右子树中找到了目标元素
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-else判断都是在判断下一次进行遍历的方向,即dir
if ((p = (dir <= 0) ? p.left : p.right) == null) {
//当p为null,说明结点要插入在这个位置
Node<K,V> xpn = xp.next;
//新建结点
TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
//根据dir就可以知道结点插入位置
if (dir <= 0)
xp.left = x;
else
xp.right = x;
//因为红黑树可能会重新转换为链表,所以我们需要维护结点的next指针
xp.next = x;
x.parent = x.prev = xp;
if (xpn != null)
((TreeNode<K,V>)xpn).prev = x;
//结点插入完要进行红黑树的调整,并调整桶中结点保证桶中的第一个结点是根结点
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}
复制代码
链表树形化
- 先判断表的容量是否达到了最小树形化容量64,如果没有,进行扩容操作。否则,将要树形化的桶中链表结点
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();
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);
//调用第一个结点的treeify方法
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
复制代码
参考