链表转树结构
根据详解四, 当链表长度大于 8 时, 为了更高效的查询, 需要转成红黑树结构, 使用的方法是 treeifyBin. 过程是先把链表结构调整为双向链表结构, 再把双向链表结构调整为红黑树结构.
1 | /** |
复制红黑树
在详解三讲 resize 方法时, 将树节点复制到新的数组使用方法 split, 这个方法同样是 HashMap 内部类 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76/**
* map: HashMap 本身
* tab: 新数组
* index: 旧数组的索引位置
* bit: 旧数组长度
*/
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
// 跟详解三一样, 先设置两对头尾节点表示两个链表
// 复制过程是先通过双向链表的遍历, 然后复制到两个链表, 参考详解三
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
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;
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;
}
}
// 数组索引位置指向链表头结点
// 链表长度小于阈值, 把树节点转成链表节点保存
// 这里阈值为 6, 跟链表转树的阈值 8 不同, 不清楚为什么要不同
// 链表长度大于等于阈值, 把链表转成树结构, 参考上面的 treeify 方法
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null) // (else is already treeified)
loHead.treeify(tab);
}
}
// 同上
// 注意这里保存的索引值要加上旧数组的长度, 为什么这样可以参考详解三
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
/**
* 把树节点转成链表节点
*/
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;
}
到这里, HashMap 插入键值对的过程就基本介绍完了, 至于获取键值对就不仔细展开讨论了, 涉及到的遍历都是一样的. 关于删除操作以后有机会再来具体分析.
如果有疑问欢迎来 Issues 探讨