源码分析
紧接上一章节,本章节主要讲解HashMap的删除和查找完整操作流程(建议本内容从头到尾看,先看源码再看流程图,否则你会看的很懵逼。)。
getTreeNode()方法源码分析
/**
* 调用根节点的find
*/
final TreeNode<K,V> getTreeNode(int h, Object k) {
//判断父节点不等于null,先找到root节点否则从当前节点开始寻找相同节点
return ((parent != null) ? root() : this).find(h, k, null);
}
split()方法源码分析
由于扩容要求太高,所以只进行流程讲解,边看边脑海构思,看完之后就差不多懂了,看过扩容操作的话你会发现和扩容差不多的逻辑
/**
* 将树箱中的节点拆分为较低和较高的树箱,
* 如果现在太小,就会被树化。 只从resize调用;
*
* @param map 当前map结构
* @param tab 新数组容器
* @param index 当前遍历的下标
* @param bit 原数组容器
*/
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; //lo(当前)头、尾节点(保持秩序)
TreeNode<K, V> hiHead = null, hiTail = null; //hi(新)头、尾节点(保持秩序)
int lc = 0, hc = 0; //原节点数量、新节点数量
for (TreeNode<K, V> e = b, next; e != null; e = next) { //从当前头节点遍历
next = (TreeNode<K, V>) e.next; //next节点等于e节点的下一个节点
e.next = null; //将遍历节点的下一个节点设为null
if ((e.hash & bit) == 0) { //如果等于0,说明还是再这个数组里面
if ((e.prev = loTail) == null) //判断当前遍历节点的上一个节点等于loTail节点
loHead = e; //头节点等于当前遍历的节点
else //尾节点不为null
loTail.next = e; //尾节点的下一个节点等于当前遍历的节点(建立双向关系)
loTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系
++lc; //原节点数累加
} else { //否则不等于0,说明再新的数组里面
if ((e.prev = hiTail) == null) //判断当前遍历节点的上一个节点等于hiTail节点
hiHead = e; //头节点等于当前遍历的节点
else //尾节点不为null
hiTail.next = e; //尾节点的下一个节点等于当前遍历的节点(建立双向关系)
hiTail = e; //尾节点等于当前遍历节点,确保每次遍历的节点与上个节点建立关系
++hc; //新节点数累加
}
}
if (loHead != null) { //(当前)尾节点不为空
if (lc <= UNTREEIFY_THRESHOLD) //原节点数小于等于6
tab[index] = loHead.untreeify(map); //解除树结构后再赋值给数组下标
else { //否则原节点数大于6
tab[index] = loHead; //数组下标等于重新建立连接后的节点
if (hiHead != null) //判断hiHead节点不等于null,等于null说明还是原来的树结构
loHead.treeify(tab); //重新开始建立树形化关系
}
}
if (hiHead != null) { //(新)尾节点不为空
if (hc <= UNTREEIFY_THRESHOLD) //新节点数小于等于6
//解除树结构后再赋值给新数组下标(当前遍历下标+原数组容器容量,反正不会超过新数组长度(2倍扩容))
tab[index + bit] = hiHead.untreeify(map);
else { //否则新节点数大于6
tab[index + bit] = hiHead; //新数组下标等于重新建立连接后的新节点
//判断loHead节点不等于null,等于null说明还是原来的树结构,只是换了个位置
if (loHead != null)
hiHead.treeify(tab); //重新开始建立树形化关系
}
}
}
remove()方法源码分析
//如果该映射存在,则从该映射中移除指定键的映射
public V remove(Object key) {
Node<K,V> e;
//返回删除节点后的节点元素如果等于null返回null否则返回节点的value值
return (e = removeNode(hash(key), key, null, false, true)) == null ?
null : e.value;
}
//删除节点 matchValue=如果为true只移除值相等的情况, movable=如果为false,移除时不要移动其他节点
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
Node<K,V>[] tab;Node<K,V> p;int n, index;
//判断数组不等于null且长度大于0且计算后的数组位置节点赋值p节点不等于null
if ((tab = table) != null && (n = tab.length) > 0 &&
(p = tab[index = (n - 1) & hash]) != null) {
Node<K,V> node = null, e; K k; V v;
//如果删除的key值和p节点的第一个节点相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
node = p; //node节点等于p节点
else if ((e = p.next) != null) { //p节点的下一个节点赋值e节点不等于null
if (p instanceof TreeNode) //如果p节点是树形化节点
//从树节点中查找节点赋值给node节点
node = ((TreeNode<K,V>) p).getTreeNode(hash, key);
else { //否则p节点不是树形化节点
do {
if (e.hash == hash &&
((k = e.key) == key ||
(key != null && key.equals(k)))) { //判断e节点和删除节点是否相同
node = e; //node节点等于e节点
break; //结束
}
p = e; //p节点等于e节点
} while ((e = e.next) != null); //e节点等于e节点的下一个节点不等于null
}
}
//如果node节点不等null且matchValue等于false或value和node的value相等
if (node != null && (!matchValue || (v = node.value) == value ||
(value != null && value.equals(v)))) {
if (node instanceof TreeNode) //node如果是树节点
//进行树形化删除
((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
else if (node == p) //如果node等于p节点
tab[index] = node.next; //数组下标元素等于node节点的下一个节点
else //否则不是树节点和p节点
p.next = node.next; //p节点的下一个节点指向node节点的下一个节点
++modCount; //操作次数加一
--size; //元素大小减一
afterNodeRemoval(node);
return node; //返回删除的node元素
}
}
return null; //没找到存在节点,返回null
}
案例讲解
removeNode()方法:小结
- 删除链表节点比较简单,主要就是通过删除的key值计算出数组位置的元素,然后从链表节点中找到删除节点的位置,如果是树节点就行树结构查找,当然删除也是进行树结构删除,其他情况如果删除节点是头节点,数组的头节点变成下一个节点,否则将删除节点的上一个节点和下一个节点建立连接,脱离删除节点。
removeTreeNode()方法源码分析
//删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0) //如果数组等于null或者数组长度等于0
return;//结束
int index = (n - 1) & hash; //通过删除元素的hash值计算下标
//数组下标第一个元素赋值为first节点和root节点
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) //判断pred节点等于null
//数组的第一个节点等于first节点等于下一个节点
tab[index] = first = succ;
else //否则不等于null
pred.next = succ; //pred节点的下一个节点等于succ节点
if (succ != null) //判断succ节点不等于null
succ.prev = pred; //判断succ的上一个节点重新指向pred节点
if (first == null) //判断first节点等于null
return; //返回
if (root.parent != null) //判断roor节点的父节点不等于null
root = root.root(); //root节点等于从root节点开始往上继续找最终的root节点
//判断root节点等于null或者右、左节点和左节点的左子节点等于null
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map); //解除树结构
return; //返回
}
//p节点=当前节点、pl节点=左子节点、pr=右子节点、replacement=替换节点
TreeNode<K, V> p = this, pl = left, pr = right, replacement;
if (pl != null && pr != null) { //判断pl、pr节点不等于null
TreeNode<K, V> s = pr, sl; //pr节点赋值给s节点
while ((sl = s.left) != null) //判断sl节点等于s节点的左子节点不等于null
s = sl; //s节点等于sl节点
boolean c = s.red;
s.red = p.red;
p.red = c; //s节点的颜色等于p节点的颜色,p节点的颜色等于s节点的颜色
TreeNode<K, V> sr = s.right; //sr节点等于s节点的右子节点
TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
if (s == pr) { //s节点等于pr节点
p.parent = s; //p节点的父节点等于s节点
s.right = p; //s节点的右子节点等于p节点
} else { //否则不相等
TreeNode<K, V> sp = s.parent; //获取s节点的父节点
if ((p.parent = sp) != null) { //判断p节点的父节点等于sp节点不等于null
if (s == sp.left) //s节点等于sp节点的左子节点
sp.left = p; //sp节点的左子节点等于p节点
else //否则为右子节点
sp.right = p; //sp节点的右子节点等于p节点
}
if ((s.right = pr) != null) //s节点的右子节点等于pr节点不等于null
pr.parent = s; //pr节点的父节点等于s节点
}
p.left = null; //将p节点的左子节点设为null
if ((p.right = sr) != null) //判断p节点的右子节点等于sr节点不等于null
sr.parent = p; //sr节点的父节点等于p节点
if ((s.left = pl) != null) //判断s节点的左子节点等于pl节点不等于null
pl.parent = s; //pl节点的父节点等于s节点
if ((s.parent = pp) == null) //判断s节点的父节点等于pp节点等于null
root = s; //root节点等于s节点
else if (p == pp.left) //判断p节点等于pp节点的左子节点
pp.left = s; //pp节点的左子节点等于s节点
else //判断p节点等于pp节点的右子节点
pp.right = s; //pp节点的右子节点等于s节点
if (sr != null) //sr节点不等于null
replacement = sr; //替换节点等于sr节点
else //否则sr节点等于null
replacement = p; //替换节点等于p节点
}
else if (pl != null) //判断pl节点不等于null
replacement = pl; //替换节点等于pl节点
else if (pr != null) //判断pr节点不等于null
replacement = pr; //替换节点等于pr节点
else //否则左右节点都为null
replacement = p; //替换节点等于p节点
if (replacement != p) { //判断替换节点不等于p节点
//pp节点等于替换节点的父节点等于p节点的父节点
TreeNode<K, V> pp = replacement.parent = p.parent;
if (pp == null) //判断pp节点等于null
root = replacement; //root节点等于替换节点
else if (p == pp.left) //p节点等于pp节点的左子节点
pp.left = replacement; //pp的左子节点等于替换节点
else //否则pp节点不等于null且p节点为右子节点
pp.right = replacement; //pp节点的右子节点为替换节点
p.left = p.right = p.parent = null; //p节点的左、右、父节点设为null
}
//p节点为红色,r节点等于root节点否则等于删除平衡后的节点
TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement);
if (replacement == p) { //判断替换节点等于p节点
TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
p.parent = null; //p节点的父节点等于null
if (pp != null) { //判断pp节点不等于null
if (p == pp.left) //判断p节点等于pp节点的左子节点
pp.left = null; //pp节点的左节点设为null
else if (p == pp.right) //判断p节点等于pp节点的右子节点
pp.right = null; //pp节点的右节点设为null
}
}
if (movable) //movable等于ture
moveRootToFront(tab, r); //删除移动
}
案例讲解
将removeTreeNode()方法切割为三个方面进行讲解。
//删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0) //如果数组等于null或者数组长度等于0
return;//结束
int index = (n - 1) & hash; //通过删除元素的hash值计算下标
//数组下标第一个元素赋值为first节点和root节点
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) //判断pred节点等于null
//数组的第一个节点等于first节点等于下一个节点
tab[index] = first = succ;
else //否则不等于null
pred.next = succ; //pred节点的下一个节点等于succ节点
if (succ != null) //判断succ节点不等于null
succ.prev = pred; //判断succ的上一个节点重新指向pred节点
if (first == null) //判断first节点等于null
return; //返回
if (root.parent != null) //判断roor节点的父节点不等于null
root = root.root(); //root节点等于从root节点开始往上继续找最终的root节点
//判断root节点等于null或者右、左节点和左节点的左子节点等于null
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map); //解除树结构
return; //返回
}
//省略部分代码...
}
//删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
//省略部分代码
//p节点=当前节点、pl节点=左子节点、pr=右子节点、replacement=替换节点
TreeNode<K, V> p = this, pl = left, pr = right, replacement;
if (pl != null && pr != null) { //判断pl、pr节点不等于null
TreeNode<K, V> s = pr, sl; //pr节点赋值给s节点
while ((sl = s.left) != null) //判断sl节点等于s节点的左子节点不等于null
s = sl; //s节点等于sl节点
boolean c = s.red;
s.red = p.red;
p.red = c; //s节点的颜色等于p节点的颜色,p节点的颜色等于s节点的颜色
TreeNode<K, V> sr = s.right; //sr节点等于s节点的右子节点
TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
if (s == pr) { //s节点等于pr节点
p.parent = s; //p节点的父节点等于s节点
s.right = p; //s节点的右子节点等于p节点
} else { //否则不相等
TreeNode<K, V> sp = s.parent; //获取s节点的父节点
if ((p.parent = sp) != null) { //判断p节点的父节点等于sp节点不等于null
if (s == sp.left) //s节点等于sp节点的左子节点
sp.left = p; //sp节点的左子节点等于p节点
else //否则为右子节点
sp.right = p; //sp节点的右子节点等于p节点
}
if ((s.right = pr) != null) //s节点的右子节点等于pr节点不等于null
pr.parent = s; //pr节点的父节点等于s节点
}
p.left = null; //将p节点的左子节点设为null
if ((p.right = sr) != null) //判断p节点的右子节点等于sr节点不等于null
sr.parent = p; //sr节点的父节点等于p节点
if ((s.left = pl) != null) //判断s节点的左子节点等于pl节点不等于null
pl.parent = s; //pl节点的父节点等于s节点
if ((s.parent = pp) == null) //判断s节点的父节点等于pp节点等于null
root = s; //root节点等于s节点
else if (p == pp.left) //判断p节点等于pp节点的左子节点
pp.left = s; //pp节点的左子节点等于s节点
else //判断p节点等于pp节点的右子节点
pp.right = s; //pp节点的右子节点等于s节点
if (sr != null) //sr节点不等于null
replacement = sr; //替换节点等于sr节点
else //否则sr节点等于null
replacement = p; //替换节点等于p节点
}
//省略部分代码...
}
//删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
//省略部分代码
else if (pl != null) //判断pl节点不等于null
replacement = pl; //替换节点等于pl节点
else if (pr != null) //判断pr节点不等于null
replacement = pr; //替换节点等于pr节点
else //否则左右节点都为null
replacement = p; //替换节点等于p节点
if (replacement != p) { //判断替换节点不等于p节点
//pp节点等于替换节点的父节点等于p节点的父节点
TreeNode<K, V> pp = replacement.parent = p.parent;
if (pp == null) //判断pp节点等于null
root = replacement; //root节点等于替换节点
else if (p == pp.left) //p节点等于pp节点的左子节点
pp.left = replacement; //pp的左子节点等于替换节点
else //否则pp节点不等于null且p节点为右子节点
pp.right = replacement; //pp节点的右子节点为替换节点
p.left = p.right = p.parent = null; //p节点的左、右、父节点设为null
}
//p节点为红色,r节点等于root节点否则等于删除平衡后的节点
TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement);
if (replacement == p) { //判断替换节点等于p节点
TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
p.parent = null; //p节点的父节点等于null
if (pp != null) { //判断pp节点不等于null
if (p == pp.left) //判断p节点等于pp节点的左子节点
pp.left = null; //pp节点的左节点设为null
else if (p == pp.right) //判断p节点等于pp节点的右子节点
pp.right = null; //pp节点的右节点设为null
}
}
if (movable) //movable等于ture
moveRootToFront(tab, r); //删除移动
}
balanceDeletion()方法源码分析
//平衡删除
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
//xp=x节点的父节点、xpl=xp节点的左子节点、xpr=xp节点的右子节点
for (TreeNode<K,V> xp, xpl, xpr; ; ) {
if (x == null || x == root) //判断x节点等于null获取x节点等于root节点
return root; //返回root节点
else if ((xp = x.parent) == null) { //判断x节点的父节点赋值给xp节点等于null
x.red = false; //x节点设为黑色
return x; //返回x节点
} else if (x.red) { //判断x节点红色
x.red = false; //将x节点设为黑色
return root; //返回root节点
} else if ((xpl = xp.left) == x) { //判断xpl节点等于xp节点的左子节点等于x节点
//xpr等于xp节点的右子节点不等于null且红色
if ((xpr = xp.right) != null && xpr.red) {
xpr.red = false; //xpr节点设为黑色
xp.red = true; //xp节点设为红色
root = rotateLeft(root, xp); //从xp节点开始左旋
//xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
xpr = (xp = x.parent) == null ? null : xp.right;
}
if (xpr == null) //判断xpr节点等于null
x = xp; //x节点等于xp节点
else //否则xpr节点不等于null
//获取xpr节点的左子节点、右子节点
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
//判断sr节点等于null或者黑色且sl等于null或者黑色
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
xpr.red = true; //xpr节点设为红色
x = xp; //x节点等于xp节点
} else { //否则sr节点或者sl节点不等于null
if (sr == null || !sr.red) { //sr节点等于null或者黑色
if (sl != null) //sl节点不等于null
sl.red = false; //sl节点设为黑色
xpr.red = true; //xpr节点设为红色
root = rotateRight(root, xpr); //右旋
//xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
xpr = (xp = x.parent) == null ? null : xp.right;
}
if (xpr != null) { //判断xpr不等于null
//xp节点等于null,xpr节点为黑色,否则为xp节点颜色
xpr.red = (xp == null) ? false : xp.red;
if ((sr = xpr.right) != null) //sr等于xpr节点的右子节点不等于null
sr.red = false; //sr节点设为黑色
}
if (xp != null) { //判断xp节点不等于null
xp.red = false; //xp节点设为黑色
root = rotateLeft(root, xp); //左旋
}
x = root; //x节点等于root节点
}
} else { //否则x节点是xp节点的右子节点
if (xpl != null && xpl.red) { //判断xpl节点不等于null且xpl节点等于红色
xpl.red = false; //xpl节点设为黑色
xp.red = true; //xp节点设为红色
root = rotateRight(root, xp); //右旋
//xp等于x节点的父节点等于null,xpl等于null否则等于xp节点的左子节点
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl == null) //判断xpl节点等于null
x = xp; //x节点等于xp节点
else { //否则xpl节点不等于null
//获取xpl节点的左子节点、右子节点
TreeNode<K,V> sl = xpl.left, sr = xpl.right;
//判断sr节点等于null或者黑色且sl等于null或者黑色
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
xpl.red = true; //xpr节点设为红色
x = xp; //x节点等于xp节点
} else { //否则sr节点或者sl节点不等于null
if (sl == null || !sl.red) { //sr节点等于null或者黑色
if (sr != null) //sl节点不等于null
sr.red = false; //sl节点设为黑色
xpl.red = true; //xpr节点设为红色
root = rotateLeft(root, xpl); //左旋
//xp等于x节点的父节点等于null,xpl等于null否则等于xp节点的左子节点
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl != null) { //判断xpl不等于null
//xp节点等于null,xpl节点为黑色,否则为xp节点颜色
xpl.red = (xp == null) ? false : xp.red;
if ((sl = xpl.left) != null) //sr等于xpl节点的右子节点不等于null
sl.red = false; //sr节点设为黑色
}
if (xp != null) { //判断xp节点不等于null
xp.red = false; //xp节点设为黑色
root = rotateRight(root, xp); //右旋
}
x = root; //x节点等于root节点
}
}
}
}
}
案例讲解
尾部节点的平衡操作
//平衡删除
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
//xp=x节点的父节点、xpl=xp节点的左子节点、xpr=xp节点的右子节点
for (TreeNode<K,V> xp, xpl, xpr; ; ) {
if (x == null || x == root) //判断x节点等于null获取x节点等于root节点
return root; //返回root节点
else if ((xp = x.parent) == null) { //判断x节点的父节点赋值给xp节点等于null
x.red = false; //x节点设为黑色
return x; //返回x节点
} else if (x.red) { //判断x节点红色
x.red = false; //将x节点设为黑色
return root; //返回root节点
} else if ((xpl = xp.left) == x) { //判断xpl节点等于xp节点的左子节点等于x节点
//xpr等于xp节点的右子节点不等于null且红色
if ((xpr = xp.right) != null && xpr.red) {
xpr.red = false; //xpr节点设为黑色
xp.red = true; //xp节点设为红色
root = rotateLeft(root, xp); //从xp节点开始左旋
//xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
xpr = (xp = x.parent) == null ? null : xp.right;
}
if (xpr == null) //判断xpr节点等于null
x = xp; //x节点等于xp节点
else //否则xpr节点不等于null
//获取xpr节点的左子节点、右子节点
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
//判断sr节点等于null或者黑色且sl等于null或者黑色
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
xpr.red = true; //xpr节点设为红色
x = xp; //x节点等于xp节点
}
//省略部分代码...
}
}
}
左旋和右旋在这里就不在详细讲解了,相信你们一眼就知道结果了
//删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
//省略部分代码
//p节点为红色,r节点等于root节点否则等于删除平衡后的节点
TreeNode<K, V> r = p.red ? root : balanceDeletion(root, replacement);
if (replacement == p) { //判断替换节点等于p节点
TreeNode<K, V> pp = p.parent; //pp节点等于p节点的父节点
p.parent = null; //p节点的父节点等于null
if (pp != null) { //判断pp节点不等于null
if (p == pp.left) //判断p节点等于pp节点的左子节点
pp.left = null; //pp节点的左节点设为null
else if (p == pp.right) //判断p节点等于pp节点的右子节点
pp.right = null; //pp节点的右节点设为null
}
}
if (movable) //movable等于ture
moveRootToFront(tab, r); //删除移动
}
/**
* 确保给定的根是其bin的第一个节点。 (移动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) { //判断root节点不等于null且数组不等于null数组长度大于0
int index = (n - 1) & root.hash; //通过root的hash值计算下标
TreeNode<K, V> first = (TreeNode<K, V>) tab[index]; //获取下标第一个节点
if (root != first) { //判断新的root节点不等于第一个节点
Node<K, V> rn; //root节点的下一个节点
tab[index] = root; //数组下标替换为root节点
TreeNode<K, V> rp = root.prev; //root节点的上一个节点
if ((rn = root.next) != null) //判断root节点的下一个节点不等于null
((TreeNode<K, V>) rn).prev = rp; //rn节点的上一个节点等于rp节点
if (rp != null) //判断rp节点不等于null
rp.next = rn; //rp节点的下一个节点等于rn节点(建立上下关系)
if (first != null) //判断first节点不等于null
first.prev = root; //first节点的上一个节点等于root
root.next = first; //root节点的下一个节点等于first
root.prev = null; //root节点的上一个节点等于null
}
assert checkInvariants(root); //检查根节点是否满足红黑树节点规则
}
}
再来讲解,中间节点的平衡操作
//删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0) //如果数组等于null或者数组长度等于0
return;//结束
int index = (n - 1) & hash; //通过删除元素的hash值计算下标
//数组下标第一个元素赋值为first节点和root节点
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) //判断pred节点等于null
//数组的第一个节点等于first节点等于下一个节点
tab[index] = first = succ;
else //否则不等于null
pred.next = succ; //pred节点的下一个节点等于succ节点
if (succ != null) //判断succ节点不等于null
succ.prev = pred; //判断succ的上一个节点重新指向pred节点
if (first == null) //判断first节点等于null
return; //返回
if (root.parent != null) //判断roor节点的父节点不等于null
root = root.root(); //root节点等于从root节点开始往上继续找最终的root节点
//判断root节点等于null或者右、左节点和左节点的左子节点等于null
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map); //解除树结构
return; //返回
}
//省略部分代码...
}
//删除数节点 map=当前map结构 tab=数组容器 movable=删除时移动其它节点
final void removeTreeNode(HashMap<K,V> map, Node<K,V>[] tab,
boolean movable) {
int n;
if (tab == null || (n = tab.length) == 0) //如果数组等于null或者数组长度等于0
return;//结束
int index = (n - 1) & hash; //通过删除元素的hash值计算下标
//数组下标第一个元素赋值为first节点和root节点
TreeNode<K, V> first = (TreeNode<K, V>) tab[index], root = first, rl;
//获取当前节点的下一个节点和上一个节点
TreeNode<K, V> succ = (TreeNode<K, V>) next, pred = prev;
//省略部分代码
//判断root节点等于null或者右、左节点和左节点的左子节点等于null
if (root == null || root.right == null ||
(rl = root.left) == null || rl.left == null) {
tab[index] = first.untreeify(map); //解除树结构
return; //返回
}
//省略部分代码...
}
untreeify()方法源码分析
//解除树形结构
final Node<K, V> untreeify(HashMap<K, V> map) {
//hd=头节点、tl=引用节点
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) //判断tl节点等于null
hd = p; //hd节点等于p节点
else //否则tl节点不等于null
tl.next = p; //tl节点的下一个节点等于p
tl = p; //tl节点等于p节点
}
return hd; //返回hd节点
}
案例讲解
removeTreeNode()方法:小结
- 删除树型节点主要分为三个步骤:第一个步骤是维护链表结构、第二个步骤是维护红黑树结构、第三个步骤是解除关系。
- 维护链表结构。和链表结构删除一样,上一个节点为空,数组的头节点变成下一个元素,否则上一个节点和下一个节点建立联系,脱离出删除节点。还有一种情况就是树节点的根节点等于null或者左右子节点等于null的情况,就会进行解除树型化结构变成链表结构(在扩容的时候也会进行链表长度判断,不达到7的都会变成链表结构)。
- 维护红黑树结构。链表结构维护好,再来维护红黑树结构,如果父节点和子节点不等于null,就会将父节点和子节点建立联系,直到删除节点变成最后一个子节点,单向切断子节点和父节点
- 解除关系。两种情况,一种是替换节点不是删除节点,这种情况先将替换节点改为删除节点然后在解除左、右、父节点的联系;另一种是替换节点等于删除节点,再维护红黑树的时候已经单向的解除了子节点与父节点的关系,然后从父节点角度,来解除与子节点的关系,这样就完成了双向关系的解除。你会发现解除关系都会将删除节点挪到最后的子节点去做删除
然后接着讲解删除平衡的剩余部分代码流程
//平衡删除
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
//xp=x节点的父节点、xpl=xp节点的左子节点、xpr=xp节点的右子节点
for (TreeNode<K,V> xp, xpl, xpr; ; ) {
if (x == null || x == root) //判断x节点等于null获取x节点等于root节点
return root; //返回root节点
else if ((xp = x.parent) == null) { //判断x节点的父节点赋值给xp节点等于null
x.red = false; //x节点设为黑色
return x; //返回x节点
} else if (x.red) { //判断x节点红色
x.red = false; //将x节点设为黑色
return root; //返回root节点
} else if ((xpl = xp.left) == x) { //判断xpl节点等于xp节点的左子节点等于x节点
//xpr等于xp节点的右子节点不等于null且红色
if ((xpr = xp.right) != null && xpr.red) {
xpr.red = false; //xpr节点设为黑色
xp.red = true; //xp节点设为红色
root = rotateLeft(root, xp); //从xp节点开始左旋
//xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
xpr = (xp = x.parent) == null ? null : xp.right;
}
if (xpr == null) //判断xpr节点等于null
x = xp; //x节点等于xp节点
else //否则xpr节点不等于null
//获取xpr节点的左子节点、右子节点
TreeNode<K,V> sl = xpr.left, sr = xpr.right;
//判断sr节点等于null或者黑色且sl等于null或者黑色
if ((sr == null || !sr.red) &&
(sl == null || !sl.red)) {
xpr.red = true; //xpr节点设为红色
x = xp; //x节点等于xp节点
} else { //否则sr节点或者sl节点不等于null
if (sr == null || !sr.red) { //sr节点等于null或者黑色
if (sl != null) //sl节点不等于null
sl.red = false; //sl节点设为黑色
xpr.red = true; //xpr节点设为红色
root = rotateRight(root, xpr); //右旋
//xp等于x节点的父节点等于null,xpr等于null否则等于xp节点的右子节点
xpr = (xp = x.parent) == null ? null : xp.right;
}
if (xpr != null) { //判断xpr不等于null
//xp节点等于null,xpr节点为黑色,否则为xp节点颜色
xpr.red = (xp == null) ? false : xp.red;
if ((sr = xpr.right) != null) //sr等于xpr节点的右子节点不等于null
sr.red = false; //sr节点设为黑色
}
if (xp != null) { //判断xp节点不等于null
xp.red = false; //xp节点设为黑色
root = rotateLeft(root, xp); //左旋
}
x = root; //x节点等于root节点
}
}
//省略部分代码...
}
}
}
上面讲解了左子节点的删除平衡,再来讲解x节点是右子节点的情况
//平衡删除
static <K,V> TreeNode<K,V> balanceDeletion(TreeNode<K,V> root,
TreeNode<K,V> x) {
//xp=x节点的父节点、xpl=xp节点的左子节点、xpr=xp节点的右子节点
for (TreeNode<K,V> xp, xpl, xpr; ; ) {
if (x == null || x == root) //判断x节点等于null获取x节点等于root节点
return root; //返回root节点
else if ((xp = x.parent) == null) { //判断x节点的父节点赋值给xp节点等于null
x.red = false; //x节点设为黑色
return x; //返回x节点
} else if (x.red) { //判断x节点红色
x.red = false; //将x节点设为黑色
return root; //返回root节点
} //省略部分代码...
else { //否则x节点是xp节点的右子节点
if (xpl != null && xpl.red) { //判断xpl节点不等于null且xpl节点等于红色
xpl.red = false; //xpl节点设为黑色
xp.red = true; //xp节点设为红色
root = rotateRight(root, xp); //右旋
//xp等于x节点的父节点等于null,xpl等于null否则等于xp节点的左子节点
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl == null) //判断xpl节点等于null
x = xp; //x节点等于xp节点
else { //否则xpl节点不等于null
//获取xpl节点的左子节点、右子节点
TreeNode<K,V> sl = xpl.left, sr = xpl.right;
//判断sr节点等于null或者黑色且sl等于null或者黑色
if ((sl == null || !sl.red) &&
(sr == null || !sr.red)) {
xpl.red = true; //xpr节点设为红色
x = xp; //x节点等于xp节点
} else { //否则sr节点或者sl节点不等于null
if (sl == null || !sl.red) { //sr节点等于null或者黑色
if (sr != null) //sl节点不等于null
sr.red = false; //sl节点设为黑色
xpl.red = true; //xpr节点设为红色
root = rotateLeft(root, xpl); //左旋
//xp等于x节点的父节点等于null,xpl等于null否则等于xp节点的左子节点
xpl = (xp = x.parent) == null ? null : xp.left;
}
if (xpl != null) { //判断xpl不等于null
//xp节点等于null,xpl节点为黑色,否则为xp节点颜色
xpl.red = (xp == null) ? false : xp.red;
if ((sl = xpl.left) != null) //sr等于xpl节点的右子节点不等于null
sl.red = false; //sr节点设为黑色
}
if (xp != null) { //判断xp节点不等于null
xp.red = false; //xp节点设为黑色
root = rotateRight(root, xp); //右旋
}
x = root; //x节点等于root节点
}
}
}
}
}
balanceDeletion()方法:小结
- 删除平衡操作,主要为了平衡过程中,将删除节点移动到最后一个节点且保持结构、颜色平衡,再进行删除。
get()方法源码分析
//通过key获取value
public V get(Object key) {
Node<K, V> e;//e节点
//调用getNode方法后,判断e节点不等于null,返回对于的value,否则返回null
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//获取节点
final Node<K,V> getNode(int hash, Object key) {
//tab=数组、first=第一个节点、e=下一个节点、n=数组长度、k=key
Node<K,V>[] tab;Node<K,V> first, e;int n;K k;
//判断table不等于null且长度大于0,first节点不等于null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//判断first节点的hash值、key等于传入的hash值、key
if (first.hash == hash &&
((k = first.key) == key || (key != null && key.equals(k))))
return first; //返回first节点
if ((e = first.next) != null) { //判断e节点等于first节点的下一个节点不等于null
if (first instanceof TreeNode) //first节点等于树形化节点
//调用树形化获取方法
return ((TreeNode<K, V>) first).getTreeNode(hash, key);
do { //不是树形化节点且下一个节点不等于null,进入循环
//判断e节点等于first节点的下一个节点不等于null
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e; //返回e节点
//判断e节点等于e节点的下一个节点不等于null,进入下次循环
} while ((e = e.next) != null);
}
}
return null;
}
//寻找左右子节点是否有相同节点
final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
TreeNode<K,V> p = this; //p=当前节点
do {
int ph, dir;
K pk; //ph=p节点的hash值、dir=存放位置遍历、p=p节点key值
TreeNode<K,V> pl = p.left, pr = p.right, q; //pl=p节点的左子节点、 pr=p节点的右子节点、q=相同节点
if ((ph = p.hash) > h) //p节点hash值大于h
p = pl; //p节点等于pl节点
else if (ph < h) // p节点hash值小于h
p = pr; // p节点等于pr节点
else if ((pk = p.key) == k || (k != null && k.equals(pk))) //key值相同
return p; //返回p节点
else if (pl == null) //pl节点等于null
p = pr; //p节点等于pr节点
else if (pr == null) //pr节点等于null
p = pl; //p节点等于pl节点
//否则不满足前几种情况,kc类似类不等于null(有类似类)或者类比较不等于0(两个类比较不相等)(不做讲解,理解即可)
else if ((kc != null || (kc = comparableClassFor(k)) != null) &&
(dir = compareComparables(kc, k, pk)) != 0)
p = (dir < 0) ? pl : pr; //dir小于0从p节点等于pl节点否则等于pr节点
else if ((q = pr.find(h, k, kc)) != null) //q节点等于pr节点开始匹配且不等于null
return q; //返回q节点
else //否则
p = pl; //p节点等于pl节点
} while (p != null); //p节点不等于null,进入循环结构
return null; // p节点等于null,节点循环,返回null
}
get()方法:小结
- 获取节点是比较简单的,主要分为链表获取和树节点获取。
- 链表获取。从第一个节点开始一直往下遍历,如果有匹配的节点就返回对应的value,否则返回null。
- 树节点获取。从当前节点遍历,由于有左右子节点区分,所以获取的时候,先判断是从左子节点还是右子节点依次往下遍历判断,如果有匹配节点返回对应的value,否则返回null。
知识扩展
什么是红黑树(为什么称为红黑树)?
红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1972年发明,在当时被称为对称二叉 B 树(symmetric binary B-trees)。红黑树的命名和Xerox PARC也有关系,当时Robert Sedgewick在施乐做访问学者的时候,施乐正好发明出激光彩色打印机,然后觉得这个打印机打出的红色很好看,就用了红色来区分结点的不同,于是就叫红黑树了,后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。
为什么使用的是红黑树而不是其它树?
AVL树和红黑树都是平衡二叉树。AVL树规定:左子树和右子树的高度差不能超过一倍,AVL树是更加严格的平衡,旋转的次数比红黑树要多,因此可以提供更快的查找速度;红黑树节点左右子树高度差长的不会超过两倍,红黑树基本在三次旋转之内达到平衡,这一特性使得红黑树的查询比AVL树慢,但是插入和删除根据不同情况,旋转的次数比AVL树要少,所以插入和删除效率较高。
B树、B+树是一种平衡多路查找树,这两种数据结构利用磁盘页的特性来构建的树结构,这样一次可以把一个磁盘的数据读入内存,减少I/O操作。B+树的结构比B树比红黑树更“矮胖”,所以查询的次数更少,红黑树在数据量不是很多的情况下,数据都会“挤在”一个页里面,这个时候遍历效率就退化成了链表。B树在插入和删除的时候涉及到磁盘页的分裂和合并操作;B+树因为所有的数据都保存在叶子节点,在插入和删除的时候除了B树的基本操作外,还要通过旋转维护叶子节点的顺序,而红黑树基本在三次旋转之内达到平衡,所以插入和删除效率较高。
为什么HashMap的数组长度是2次幂(为什么初始化容器值设为16)?
为了存取高效,要尽量减少碰撞,就是要尽量把数据分配均匀,通过公式(n - 1) & hash 代替取模运算提高效率,具体讲解如下:
结果显而易见
为什么HashMap的加载因子是0.75?
提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小,加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。
HashMap什么时候转化为树结构?
当链表长度大于8且数组长度大于64,数组长度不满足64会进行扩容。
为什么当链表长度大于8的时候进行树形化?
根据官方源码注释:如果 hashCode的分布离散良好的话,那么红黑树是很少会被用到的,理想情况下遵循泊松分布,使得各种长度命中的概率非常小,从而转换为红黑树的概率也小
为什么当树长度小于6的时候转换为链表?
当数组元素个数大于阈值时,需要扩容,判断链表长度小于等于6,接触树结构。主要是避免树与链表之间的频繁转换,如果等于7,删除一个元素红黑树就必须退化为链表,增加一个元素就必须树形化,浪费资源,影响性能。6主要起一个过渡作用。转换为链表还有一种情况就是红黑树删除元素的时候,如果树的节点小于3的时候也会转为链表。