源码分析之HashMap的红黑树实现

JDK1.8中,HashMap底层是用数组Node<K,V>数组存储,数组中每个元素用链表存储元素,当元素超过8个时,将链表转化成红黑树存储。

红黑树

红黑树本质上是平衡查找二叉树,用于存储有序数据,相对于链表数据查找来说,链表的时间复杂度O(n),红黑树的时间复杂度O(lgn)

红黑树需要满足一下五种特性:

  • 每个节点是红色或者黑色
  • 根节点是黑色
  • 每一个空叶子节点必须是黑色
  • 红色节点的子节点必须是黑色
  • 节点中到达任意子节点包含相同数组的黑节点

当新增节点或者减少节点,红黑树会通过左旋或者右旋操作来调整树结构,使其满足以上特性。

TreeNode 类

HashMap中红黑树是通过TreeNode类构造的。TreeNodeHashMap的静态内部类,继承与LinkedHashMap.Entry<K,V>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
...
}

treeifyBin 方法

HashMapput方法时候,但数组中某个位置的链表长度大于8时,会调用treeifyBin方法将链表转化为红黑树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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)
// 数组大小小于64的,调用resize将数组大小扩容至2倍大小
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
// 遍历链表,将链表元素转化成TreeNode链
do {
// 调用replacementTreeNode构造TreeNode
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
// TreeNode链为空,将元素设置为hd的首个节点
hd = p;
else {
// TreeNode链不为空,向TreeNode链后面添加元素
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
// TreeNode链表转化为红黑树
hd.treeify(tab);
}
}

构成红黑树–treeify 方法

treeify方法是TreeNode类的方法,作用是将Treenode链转化成红黑树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
final void treeify(Node<K,V>[] tab) {
TreeNode<K,V> root = null;
for (TreeNode<K,V> x = this, next; x != null; x = next) {
// 遍历TreeNode链
next = (TreeNode<K,V>)x.next;
x.left = x.right = null;
if (root == null) {
// 设置root节点
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;
// hashMap元素的hash值用来表示红黑树中节点数值大小
if ((ph = p.hash) > h)
// 当前节点值小于根节点,dir = -1
dir = -1;
else if (ph < h)
// 当前节点值大于根节点, dir = 1
dir = 1;
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
// 当前节点的值等于根节点值。
// 如果当前节点实现Comparable接口,调用compareTo比较大小并赋值dir
// 如果当前节点没有实现Comparable接口,compareTo结果等于0,则调用tieBreakOrder继续比较大小
// tieBreakOrder本质是通过比较k与pk的hashcode
dir = tieBreakOrder(k, pk);
// 当前“根节点”赋值给xp
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;
}
}
}
}
// 确保红黑树根节点是数组中该index的第一个节点
moveRootToFront(tab, root);
}

新增元素后平衡红黑树–balanceInsertion方法

当红黑树中新增节点的时候需要调用balanceInsertion方法来保证红黑树的特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
// 新增节点默认是红色
x.red = true;
// xp父节点 xpp祖父节点 xppl祖父左节点 xppr 祖父右节点
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
if ((xp = x.parent) == null) {
// x的父节点为空,x应为根节点,应为黑色
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) {
// x为父节点右节点,则要进行左旋操作
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
// 经过左旋x为左节点
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;
}
// x为右节点
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
// 以祖父节点为支点左旋
root = rotateLeft(root, xpp);
}
}
}
}
}
}

向红黑树添加元素 – putTreeVal 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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;;) {
// 从root节点开始遍历
int dir, ph; K pk;
// 通过比较hash大小确定添加元素的位置
if ((ph = p.hash) > h)
dir = -1;
else if (ph < h)
dir = 1;
else if ((pk = p.key) == k || (k != null && k.equals(pk)))
// key相同直接返回
return p;
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;
}
dir = tieBreakOrder(k, pk);
}
TreeNode<K,V> xp = p;
// 根据dir大小添加元素
if ((p = (dir <= 0) ? p.left : p.right) == null) {
Node<K,V> xpn = xp.next;
// 构建新的treeNode节点
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是index处首节点
moveRootToFront(tab, balanceInsertion(root, x));
return null;
}
}
}

TreeNode类还有很多方法如删除红黑树节点方法removeTreeNode,删除后平衡二叉树方法balanceDeletion,查找红黑树节点方法getTreeNode…后续有精力再来分析。