1.前言
treeify() : 树化,即当桶中元素数量元素大于等于8时且 hash表的容量>= 64时。
untreeify : 取消树化,即桶中元素小于等于6时。
这里详细的变量定义我在前面 java HashMap 详解 -- (默认常量和构造函数)
2.源码讲解
在正式讲解源码之前我们先看一下,TreeNode和Node的关系
TreeNode 是 Node 的子类。
2.1 untreeify()
这里主要作用就是,当扩容后,桶中的元素数量 <= 6时,会把它从红黑树结构转成链表结构
- 这个方法调用它的是一个 TreeNode 对象
- 首先定义头结点: hd, 和尾结点:tl
- 这里其实很简单,不停地遍历 this对象(一个TreeNode 对象),如果头结点为null,就把它赋给头结点,否则把它赋给头结点的next。
- 最终把生成的 节点hd 返回出去即可。
2.2 treeify()
这里主要作用就是 当链表长度大于等于8时,且hash表的容量大于64 会把链表结构 转成红黑树
final void treeify(Node<K,V>[] tab) {
// 首先定义一个 根节点
TreeNode<K,V> root = null;
// 这里调用对象通常是 Node对象,遍历Node
for (TreeNode<K,V> x = this, next; x != null; x = next) {
// 遍历的是链表,next 记录当前元素指向的下一个元素
next = (TreeNode<K,V>)x.next;
// 把x的左子树和右子树置null
x.left = x.right = null;
// 如果 根节点还是null
if (root == null) {
// 把x的parent 置null
x.parent = null;
// 标记x的颜色是黑
x.red = false;
// 把x赋给 root
root = x;
}
// 这里处理根节点不是 null 的情况
else {
// k:保存当前链表节点的key
K k = x.key;
// h:保存当前节点的hash
int h = x.hash;
// kc: 当前key所属的 Class
Class<?> kc = null;
// 从根节点开始遍历,寻找插入位置
for (TreeNode<K,V> p = root;;) {
// dir:记录插入的放向,-1是左侧,1是右侧,ph:记录当前红黑树节点的hash值
int dir, ph;
// 当前遍历的节点的 key
K pk = p.key;
如果 当前红黑树节点的hash值大于 当前链表节点的hash值,返回-1,表示插入当前红黑树的左边
if ((ph = p.hash) > h)
dir = -1;
// 否则 当ph<h时,表示插入右边
else if (ph < h)
dir = 1;
// 即 ph=h,他们两的hash值相等。这里会先判断他们有没有实现 Comparable 接口,如果实现了,
// 则 调用compareTo方法进行比较,若他们结果还是相等,则最终调用 tieBreakOrder 这个方法来比较。
else if ((kc == null &&
(kc = comparableClassFor(k)) == null) ||
(dir = compareComparables(kc, k, pk)) == 0)
dir = tieBreakOrder(k, pk);
// 保存当前树的节点
TreeNode<K,V> xp = p;
// 这里判断dir <= 0 时 说明他会被插入到 当前红黑树节点的左边(不一定是左子节点,
//只要在这个节点的左半边就行),同理 dir>0 表示它在当前红黑树节点的右边。
// 如果插入的是右边就把 p.right赋给 p,插入左边就判断 p.left赋给 p ,并判断p == null?
// 如果p != null,则说明xp不是叶子节点,则继续循环,向下寻找节点插入。
// 如果 p==null ,就说明xp是一个叶子节点,则把x.parent置为 xp,然后根据 dir大小判断是x置为xp的左子节点还是右子节点。
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);
// 跳出当前for循环,继续插入链表中 下一个节点
break;
}
}
}
}
// 把红黑树的根节点设为 其所在的数组槽 的第一个元素 2.4详解
moveRootToFront(tab, root);
}
2.3 tieBreakOrder(k, pk)
这个方法主要是处理hash值比较和compareTo 方法比较后结果仍然相等两个对象,进行粗略比较
static int tieBreakOrder(Object a, Object b) {
int d;
// 先进行a和b的类名进行比较,若还是相等进入
if (a == null || b == null ||
(d = a.getClass().getName().
compareTo(b.getClass().getName())) == 0)
// 通过这两个对象的hashCode进行粗略比较,即相等返回 -1
d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
-1 : 1);
return d;
}
2.4 moveRootToFront(tab, root)
这个方法的主要作用就是其所在的数组槽的第一个元素是 root节点
static <K,V> void moveRootToFront(Node<K,V>[] tab, TreeNode<K,V> root) {
// n:记录table的length
int n;
if (root != null && tab != null && (n = tab.length) > 0) {
// index : root最终放到数组的索引位置
int index = (n - 1) & root.hash;
// 取出数组 索引为index 的元素,判断它和root是否相等,相等的话就是正常的无需处理;
// 否则就需要进行处理。
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
if (root != first) {
Node<K,V> rn;
tab[index] = root;
// rp:记录root节点前置节点
TreeNode<K,V> rp = root.prev;
// 如果 root的next节点不为null,把它赋给rn
if ((rn = root.next) != null)
// 把rn的前置节点设为 rp
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
// 当 rp不为null 把rp的后置节点设为rn
rp.next = rn;
if (first != null)
// 当 first不为null ,就把root设为 frist的前置节点
first.prev = root;
// root的后置节点设为 frist
root.next = first;
// root的前置节点设为 null
root.prev = null;
}
// 这里主要是检查当前红黑树的结构是否满足红黑树的性质。
assert checkInvariants(root);
}
}
static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode<K,V>)t.next;
if (tb != null && tb.next != t)
return false;
if (tn != null && tn.prev != t)
return false;
if (tp != null && t != tp.left && t != tp.right)
return false;
if (tl != null && (tl.parent != t || tl.hash > t.hash))
return false;
if (tr != null && (tr.parent != t || tr.hash < t.hash))
return false;
if (t.red && tl != null && tl.red && tr != null && tr.red)
return false;
if (tl != null && !checkInvariants(tl))
return false;
if (tr != null && !checkInvariants(tr))
return false;
return true;
}
}