JDK1.8HashMap源码分析:JDK1.8中HashMap源码分析_Java_AC的博客-CSDN博客
书接上文在JDK1.8HashMap中引入了红黑树,当链表长度大于等于TREEIFY_THRESHOLD - 1,并且数组长度大于等于MIN_TREEIFY_CAPACITY是就会转红黑树。
static final int TREEIFY_THRESHOLD = 8;
static final int MIN_TREEIFY_CAPACITY = 64;
final void treeifyBin(Node < K, V > [] tab, int hash)
{
int n, index;
Node < K, V > e;
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);
if((tab[index] = hd) != null)
hd.treeify(tab);
}
}
首先会讲Node(单向链表)转化为TreeNode(双向列表),然后执行treeify方法,转为红黑树
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;
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;
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);
}
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;;)
{
if((xp = x.parent) == null)
{
x.red = false;
return x;
}
else if(!xp.red || (xpp = xp.parent) == null)
return root;
if(xp == (xppl = xpp.left))
{
if((xppr = xpp.right) != null && xppr.red)
{
xppr.red = false;
xp.red = false;
xpp.red = true;
x = xpp;
}
else
{
if(x == xp.right)
{
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
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);
}
}
}
}
}
}
假设双向链表的元素为[57, 73, 86, 29, 10, 96, 53, 67, 49, 32],并且每个元素的key、value和hash都为该数值!!!
第一次循环:
第二次循环:
第三次循环:
第四次循环:
第五次循环:
第六次循环:
第七次循环:
第八次循环:
第九次循环:
第十次循环:
以上为链表转红黑树的全部过程。
红黑树特性:
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
那么红黑树是怎么满足以上特性的呢?答案是左旋和右旋。
//左旋
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;
}
左旋:
- 当前节点的右节点不为空,将当前节点的右节点的左节点赋值给当前节点右节点,如果不为空,将当前节点赋值为父节点
- 将当前节点的父节点赋值给右节点的父节点,如果为空,右节点赋值给root,并且将red设置为false
- 如果当前节点的父节点的右节点等于当前节点,则将当前节点的父节点的右节点赋值为当前节点的左节点
- 否则当前节点的父节点的左节点赋值为当前节点的左节点
- 将当前节点的右节点的左节点赋值为当前节点
- 并且当前节点的父节点赋值为当前节点的右节点
事例:用第三次循环转化为例,当红黑树插入86时没有执行balanceInsertion方法前
执行balanceInsertion方法后:
然后执行 rotateLeft左旋:
- rl = p.right = r.left = null
- pp = r.parent = p.parent = null
所以(root = r).red = false;r.left = p;p.parent = r; 最终左旋后的效果为:
左旋前后对比图:
在执行完balanceInsertion方法后,进入到rotateLeft方法时,root为57,当前节点为57
- 首先将当前节点的右节点的左节点赋值给当前节点右节点,也就是57的右节点赋值为null
- 将当前节点的父节点赋值给右节点的父节点,如果为空,右节点赋值给root,并且将red设置为false,也就是将57的父节点(null)赋值给73的父节点,因为等于null,所以root赋值为73,并将73设置为非红(黑色)
- 将当前节点的右节点的左节点赋值为当前节点,也就是将57赋值给73的左节点
- 并且当前节点的父节点赋值为前节点的右节点,也就是57的父节点赋值为73
//右旋
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;
}
右旋:
- 当前节点的左节点不为空,将当前节点的左节点的右节点赋值给当前节点左节点,若果不为空,将当前节点赋值为父节点
- 将当前节点的父节点赋值给左节点的父节点,如果为空,左节点赋值给root,并且将red设置为false
- 如果当前节点的父节点的右节点等于当前节点,则将当前节点的父节点的右节点赋值为当前节点的左节点
- 否则当前节点的父节点的左节点赋值为当前节点的左节点
- 将当前节点的左节点的右节点赋值为当前节点
- 并且当前节点的父节点赋值为当前节点的左节点
事例:用第五次循环转化为例,当红黑树插入10时没有执行balanceInsertion方法前
执行balanceInsertion方法后:
然后执行 rotateRight右旋:
- lr = p.left = l.right = null
- pp = l.parent = p.parent = 73
- pp.left = l = 29
- l.right = p;p.parent = l; 最终效果为:
右旋前后对比图
在执行完balanceInsertion方法后,进入到rotateRight方法时,root为73,当前节点为57
- 首先将当前节点的左节点的右节点赋值给当前节点左节点,也就是57的座节点赋值为null
- 将当前节点的父节点赋值给左节点的父节点,也就是将29的父节点赋值为73
- 将当前节点的父节点的左节点赋值为当前节点的左节点,也就是将73的左节点赋值为29
- 将当前节点的左节点的右节点赋值为当前节点,也就是将29的右节点赋值为57
- 将当前节点的父节点赋值为当前节点的左节点,也就是将57的父节点赋值为29
以上为个人对HashMap红黑树左旋和右旋的理解,如有不合理请指出,多多交流!!!