1. 前言
HashMap(4)中介绍了HashMap中对于红黑树的一些操作,主要是remove操作,同样在这一篇中对于源码的红黑树操作不会详细介绍,在最后一篇的C语言实现HashMap中会解释红黑树的插入删除的原理这些,这篇是对这些方法的一个大概了解
第一篇文章:源码解析系列:HashMap(1)
第二篇文章:源码解析系列:HashMap(2)
第三篇文章:源码解析系列:HashMap(3)
第四篇文章:源码解析系列:HashMap(4)
第五篇文章:源码解析系列:HashMap(5)
2. 方法解析
1. removeTreeNode
1、官方
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
//n:数组长度
int n;
//table数组没有东西直接返回
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;
//succ :当前结点的下一个结点 pred :当前结点的前一个结点
TreeNode<K,V> succ = (TreeNode<K,V>)next, pred = prev;
//前一个结点为NULL
if (pred == null)
//证明是第一个结点
tab[index] = first = succ;
else
//否则就是一个链表了
pred.next = succ;
if (succ != null)
//同样比如a-c-b,要删除c
//就把b的前一个设置为a,a的下一个设置为b
succ.prev = pred;
//如果第一个是空,直接返回,证明没有数据
if (first == null)
return;
//重新找根结点,root指向第一个结点,有可能此时的第一个链表结点不是根
if (root.parent != null)
root = root.root();
//下面几种情况表示节点数小于=6
//1. root是空
//2. root的右节点为空
// B
// /
// R
//3. root的左节点为空,那么和上面一样了
//4. root的左孩子的左孩子为空
// 那么下面这种情况为什么不可以存在呢?没想明白这点
// B
// / \
// B R
// / \
// B B
// / \
// R R
//猜测可能是在删除到这里的时候对下面三行的树进行调整时,把第根结点的R换成了B
if (root == null
|| (movable
&& (root.right == null
|| (rl = root.left) == null
|| rl.left == null))) {
tab[index] = first.untreeify(map); // too small
return;
}
//下面开始删除结点,下面有时间再填坑把,这里我给出我写的c语言实现的删除
TreeNode<K,V> p = this, pl = left, pr = right, replacement;
if (pl != null && pr != null) {
TreeNode<K,V> s = pr, sl;
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;
if (sr != null)
replacement = sr;
else
replacement = p;
}
else if (pl != null)
replacement = pl;
else if (pr != null)
replacement = pr;
else
replacement = 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.left = p.right = p.parent = null;
}
TreeNode<K,V> r = p.red ? root : balanceDeletion(root, replacement);
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);
}
2、自己写的一个删除方法:删除和删除调整(c语言)
当时在做课设的时候官方的看不太懂,所以就自己去找了别人的代码和教程来看结合来写了一个。源码是把维护双链表和删除结点放在一起,我这里时分成三部分:维护双向链表-删除结点,并用后继节点进行替换-删除修复红黑树。完整的代码在后面的c语言实现也会给出。
1、维护双链表
//删除叶子结点
void deleteNode(hashmap& hashmap, int hash, Key key, Value value, hashNode& root, hashNode node) {
if (hashmap == NULL || hashmap->table == NULL || hashmap->length == 0) {
return;
}
//获取下标
int index = (hashmap->length - 1) & hash;
hashNode table = hashmap->table;
//首个节点
hashNode first = table[index].next;
hashNode realRoot = first;
//succ-调用这个方法的节点(待删除节点)的后驱节点
hashNode succ = node->next;
//prev - 调用这个方法的节点(待删除节点)的前驱节点
hashNode pred = node->prev;
/**
* 维护双向链表(map在红黑树数据存储的过程中,除了维护红黑树之外还对双向链表进行了维护)
* 从链表中将该节点删除
* 如果前驱节点为空,说明删除节点是头节点,删除之后,头节点直接指向了删除节点的后继节点
*/
if (pred->data.value == UN_EXIT) {
table[index].next = first = succ;
}
else {
//删除的不是头节点
pred->next = succ;
}
if (succ != NULL) {
succ->prev = pred;
}
// 如果头节点(即根节点)为空,说明该节点删除后,红黑树为空,直接返回
if (first == NULL) {
return;
}
// 如果父节点不为空,说明删除后,调用root方法重新获取当前树的根节点
if (first->parent != NULL) {
realRoot = getRoot(first);
}
/**
* 当以下三个条件任一满足时,当满足红黑树条件时,说明该位置元素的长度少于6(UNTREEIFY_THRESHOLD),需要对该位置元素链表化
* 1、root == NULL:根节点为空,树节点数量为0
* 2、root.rchild == NULL:右孩子为空,树节点数量最多为2
* 3、(rl = root.left) == null || rl.left == null):
* (rl = root.left) == null:左孩子为空,树节点数最多为2
* rl.left == null:左孩子的左孩子为NULL,树节点数最多为6
*/
if (hashmap->size <= UNTREEIFY_THRESHOLD) {
table[index].next = untreeify(hashmap, first);
hashmap->table = table;
return;
}
hashNode var = package(key, value);
//否则还是树,调用树的删除
deleteNodeReal(var, root, hashmap);
//重新赋值table
hashmap->table = table;
//重新赋值根节点
if (root->parent != NULL) {
root = realRoot;
}
}
2、删除树结点,并且使用后继结点替换
//真正的删除
void deleteNodeReal(hashNode node, hashNode& root, hashmap& hashMap) {
hashNode x = NULL; // x 指向 y 的唯一子节点或指向 NULL
hashNode z = searchNode(node->data.key, root);
hashNode parent = NULL;
hashNode y = z; //y 为从树中删除的节点或者移至树内的节点
int y_color = -1; //保存y的颜色
if (z == NULL) {
//没找到
return;
}
//y的颜色
y_color = y->isRed;
if (z->lchild == NULL) {
//情况1:被删除的z左节点是空,右孩子无限制,使用右孩子替换
x = z->rchild;
//使用z的右孩子替换
transplate(z, z->rchild, root);
parent = z->parent;
}
else if (z->rchild == NULL) {
//情况2:右孩子为空,使用左孩子替换
x = z->lchild;
//使用z的右孩子替换
transplate(z, z->lchild , root);
parent = z->parent;
}
else {
//z有两个结点
/*
①、找到结点D的后继结点S
②、将结点S的key值赋给结点D;
③、再将结点S从树中删除,并用结点S的右孩子替代结点S的位置;[注:从前面的描述可以看出,其实被删的是结点D的后继结点S]
④、如果被删结点S为红色,则红黑树性质未被破坏,因此不需做其他调整;
⑤、如果被删结点S为黑色,则需进一步做调整处理。
*/
//y是找到的后继结点,y的左孩子一定是NULL
y = searchMin(z->rchild);
//记录被删除的颜色
y_color = y->isRed;
x = y->rchild;
if (y->parent == z) {
//z的后继节点是y,证明y是z的右孩子结点,并且y没有左孩子结点
if (x == NULL) {
y->parent->rchild = NULL;
}
else {
x->parent = y;
}
//y取代z
transplate(z, y, root);
y->lchild = z->lchild;
y->lchild->parent = y;
y->isRed = z->isRed;
//这里的parent是因为直接取到z的孩子结点
parent = y;
}
else {
//y取代z
hashNode yRchild = y->rchild;
parent = y->parent;
//如果右结点为null,那么直接赋值
if (yRchild == NULL) {
y->parent->lchild = NULL;
}
else {
//否则就替换2次
transplate(y, yRchild, root);
}
transplate(z, y, root);
//y结点要替换到z结点的位置,赋值右子树
y->lchild = z->lchild;
y->lchild->parent = y;
y->isRed = z->isRed;
y->rchild = z->rchild;
y->rchild->parent = y;
}
}
//判断如果删除的结点是黑结点,进行修复
//对x进行修复,因为x是填充到y位置的
if (y_color == BLACK) {
deleteFixUp(x, parent, root);
}
//删除之后要调整头部
moveRootToFront(hashMap->table, root, hashMap);
//释放删除的结点z
free(z);
z = NULL;
return;
}
3、删除修复
//删除修复
//如果删除的结点时红色结点,不用调整平衡,如果删除的结点时黑节点,就要调整
/*
前提1:参照结点N为父结点P的左孩子
情况1:参照结点N的兄弟B是红色的
处理过程:
①、将父结点P的颜色改为红色,兄弟结点的颜色改为黑色;
②、以父结点P为支点进行左旋处理;
③、情况1转变为情况2或3、4,后续需要依次判断处理。
情况2:参照结点N的兄弟B是黑色的,且B的两个孩子都是黑色的
处理过程:
①、如果parent此时为红,则把brother的黑色转移到parent上
②、如果此时parent为黑,即此时全黑了,则把brother涂红,导致brother分支少一个黑,使整个分支都少了一个黑,需要对parent又进行一轮调整
情况3:参照结点N的兄弟B是黑色的,且B的左孩子是红色的,右孩子是黑色的
处理过程:
①、将兄弟结点的左孩子调整为父颜色,父颜色调整为黑色
②、以兄弟结点开始右旋
③、以父节点左旋
情况4:参照结点N的兄弟B是黑色的,且B的左孩子是黑色的,右孩子是红色的
处理过程:
①、将父结点P的颜色拷贝给兄弟结点B,再将父结点P和兄弟结点的右孩子BR的颜色改为黑色;
②、以父结点P为支点,进行左旋处理;
③、将该结点改为树的根结点,也意味着调整结束。
前提2:参照结点N为父结点P的右孩子
情况5:参照结点N的兄弟B是红色的
处理过程:
①、将父结点P的颜色改为红色,兄弟结点的颜色改为黑色;
②、以父结点P为支点进行右旋处理;
③、情况5转变为情况6或7、8,后续需要依次判断处理。
情况6:参照结点N的兄弟B是黑色的,且B的两个孩子都是黑色的
处理过程:
①、如果parent此时为红,则把brother的黑色转移到parent上
②、如果此时parent为黑,即此时全黑了,则把brother涂红,导致brother分支少一个黑,使整个分支都少了一个黑,需要对parent又进行一轮调整
情况7:参照结点N的兄弟B是黑色的,且B的右孩子是红色的,左孩子是黑色的
处理过程:
①、将兄弟结点的右孩子调整为父颜色,父颜色调整为黑色
②、以兄弟结点开始左旋
③、以父节点右旋
情况8:参照结点N的兄弟B是黑色的,且B的右孩子是黑色的,左孩子是红色的
处理过程:
①、将父结点P的颜色拷贝给兄弟结点B,再将父结点P和兄弟结点的右孩子BR的颜色改为黑色;
②、以父结点P为支点,进行右旋处理;
③、将该结点改为树的根结点,也意味着调整结束。
*/
void deleteFixUp(hashNode x, hashNode parent, hashNode& root) {
hashNode brother = NULL;
//如果要修复的树是根,由于没有兄弟姐弟啊,那么把x的颜色改成黑色就好了
while ((x == NULL || x->isRed == BLACK) && x != root) {
// 前提1:参照结点x为父结点P的左孩子
hashNode p = parent;
if (x == p->lchild) {
//兄弟是右结点
brother = p->rchild;
//情况1:参照结点N的兄弟B是红色的
/*
①、将父结点P的颜色改为红色,兄弟结点的颜色改为黑色;
②、以父结点P为支点进行左旋处理;
③、情况1转变为情况2或3、4,后续需要依次判断处理。
*/
if (brother != NULL && brother->isRed == RED) {
brother->isRed = BLACK;
p->isRed = RED;
//以父节点左旋
leftRotate(root, p);
//完成之后兄弟要重新更新进行下一步操作
brother = p->rchild;
}
// 情况2:参照结点N的兄弟B是黑色的,且B的两个孩子都是黑色的
/*
①、将兄弟结点B的颜色改为红色
②、情况2处理完成后,不必再进行情况3、4的判断,但需重新循环判断前提1、2。
*/
if (brother != NULL && brother->isRed == BLACK && (brother->lchild == NULL || isBlack(brother->lchild)) && (brother->rchild == NULL || isBlack(brother->rchild))) {
brother->isRed = RED;
// 如果parent此时为红,则把brother的黑色转移到parent上
if (p->isRed = RED) {
p->isRed = BLACK;
brother->isRed = RED;
break;
}
else {
// 如果此时parent为黑,即此时全黑了,则把brother涂红,导致brother分支少一个黑,使整个分支都少了一个黑,需要对parent又进行一轮调整
brother->isRed = RED;
x = parent;
parent = x->parent;
}
}
else {
// 情况3:参照结点N的兄弟B是黑色的,且B的左孩子是红色的
/*
①、将兄弟结点的左孩子调整为父颜色,父颜色调整为黑色
②、以兄弟结点开始右旋
③、以父节点左旋
*/
if (brother != NULL && brother->isRed == BLACK && RedOrNot(brother->lchild)) {
brother->lchild->isRed = p->isRed;
p->isRed = BLACK;
rightRotate(root, brother);
leftRotate(root, parent);
}
//情况4:参照结点N的兄弟B是黑色的,且B的左孩子是黑色的,右孩子是红色的
/*
①、将父结点P的颜色拷贝给兄弟结点B,再将父结点P和兄弟结点的右孩子BR的颜色改为黑色;
②、以父结点P为支点,进行左旋处理;
③、将该结点改为树的根结点,也意味着调整结束。
*/
else if (brother != NULL && brother->isRed == BLACK && RedOrNot(brother->rchild)) {
brother->isRed = p->isRed;
p->isRed = BLACK;
brother->rchild->isRed = BLACK;
leftRotate(root, p);
}
break;
}
}
// 前提2:参照结点x为父结点P的左孩子
else {
//兄弟是左结点
brother = p->lchild;
//情况5:参照结点N的兄弟B是红色的
/*
①、将父结点P的颜色改为红色,兄弟结点的颜色改为黑色;
②、以父结点P为支点进行右旋处理;
③、情况5转变为情况6或7、8,后续需要依次判断处理。
*/
if (brother->isRed == RED) {
p->isRed = RED;
brother->isRed = BLACK;
rightRotate(root, p);
brother = p ->lchild;
}
//情况6:参照结点N的兄弟B是黑色的,且B的两个孩子都是黑色的
/*
①、将兄弟结点B的颜色改为红色;
②、情况6处理完成后,不必再进行情况7、8的判断,但需要重新循环判断前提1、2。
*/
if ((brother->lchild == NULL || brother->lchild->isRed == BLACK)
&& (brother->rchild == NULL || brother->rchild->isRed == BLACK)) {
// 如果parent此时为红,则把brother的黑色转移到parent上
if (p->isRed == RED) {
p->isRed = BLACK;
brother->isRed = RED;
break;
}
// 如果此时parent为黑,即此时全黑了,则把brother涂红,导致brother分支少一个黑,使整个分支都少了一个黑,需要对parent又进行一轮调整
else {
brother->isRed = RED;
x = parent;
parent = x->parent;
}
}
else {
//左右孩子必有一个是红色
// 情况7:参照结点N的兄弟B是黑色的,且B的右孩子是红色的
/*
①、将兄弟结点的右孩子调整为父颜色,父颜色调整为黑色
②、以兄弟结点开始左旋
③、以父节点右旋
*/
if (brother->isRed == BLACK && brother->rchild != NULL && brother->rchild->isRed == RED) {
brother->rchild->isRed = p->isRed;
p->isRed = BLACK;
leftRotate(root, brother);
rightRotate(root, p);
}
//左右孩子必有一个是红色
// 情况8:参照结点N的兄弟B是黑色的,且B的左孩子是红色的
/*
①、将父结点P的颜色拷贝给兄弟结点B,再将父结点P和兄弟结点的右孩子BR的颜色改为黑色;
②、以父结点P为支点,进行右旋处理;
③、将该结点改为树的根结点,也意味着调整结束。
*/
else if (brother != NULL && brother->isRed == BLACK && brother->lchild != NULL && RedOrNot(brother->lchild)) {
brother->isRed = p->isRed;
p->isRed = BLACK;
brother->lchild->isRed = BLACK;
//以父节点进行旋转
rightRotate(root, p);
}
}
}
}
//修改x颜色为黑色
if (x != NULL) {
x->isRed = BLACK;
}
return;
}
2. untreeif
去树化,把红黑树去树化变成一个单向链表,当不高于去树化阈值6的时候就调用该方法。单链表的形成是根据原来红黑树中双链表来形成的。
final Node<K,V> untreeify(HashMap<K,V> map) {
//两个赋值结点hd,tl
Node<K,V> hd = null, tl = null;
//q是当前结点
for (Node<K,V> q = this; q != null; q = q.next) {
//使用普通的链表结点代替树结点,此时已经把双向链表和树的一些结点全部清为NULL
//比如prev,pred,lchild,rchild
Node<K,V> p = map.replacementNode(q, null);
//如果t1 == null,第一次进入
if (tl == null)
//p作为头节点
hd = p;
else
//next是单链表也有的,不会清除掉
tl.next = p;
tl = p;
}
//hd是单链表的头结点
return hd;
}
3. split
分散树的结点,在作为红黑树进行数组扩容的时候,调用这个方法把原来树的结点分散到指定的下标上面。
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
//b是当前结点
TreeNode<K,V> b = this;
//低位头结点尾节点,高位头结点和尾结点
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
//低位结点个数和高位结点个数
int lc = 0, hc = 0;
//下面就是遍历双向链表了,其实下面也没涉及到红黑树的破坏,只是说把双向两表的结点分开,具体怎么分可以看第一篇HashMa,里面有详细介绍
//e是当前结点
for (TreeNode<K,V> e = b, next; e != null; e = next) {
//next是e的下一个
next = (TreeNode<K,V>)e.next;
//e的下一个设置尾null
e.next = null;
//判断是高位还是低位
if ((e.hash & bit) == 0) {
//设置低位链表
if ((e.prev = loTail) == null)
//e的前一个设置尾null,这里也是第一次访问才可以进来设置头,loTail一开始肯定是null
loHead = e;
else
//设置链表尾
loTail.next = e;
loTail = e;
//低位链表数目+1
++lc;
}
else {
//高位链表的设置,和低位一样,就不多说了
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
//如果低位不为空
if (loHead != null) {
//判断如果低位的结点个数小于=6个,就去树化成为单链表
//注意此时的低位链表和高位链表还保留红黑树的特征,因为是从树退化下来的
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
//否则就设置是否要树化
tab[index] = loHead;
//高位头不为null,证明此时已经不是一棵完整的树了,要重新树化
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
//如果高位不为空
if (hiHead != null) {
//如果数量 <= 6
if (hc <= UNTREEIFY_THRESHOLD)
//去树化
tab[index + bit] = hiHead.untreeify(map);
else {
//否则设置为tab[index + bit],bit是旧数组容量
tab[index + bit] = hiHead;
//低位链表不为空,证明此时已经不是一棵完整的树了,要重新树化
if (loHead != null)
hiHead.treeify(tab);
}
}
}
如有错误,欢迎指出