距离上一篇分析HashMap的文章已经过去一年了,今天偶尔翻到之前的那篇,还记得当时是打算另起一篇来分析链表的树化,以及树的链表化过程,结果这一拖就???,还是来把坑填上吧,毕竟要有始有终。
在我看来HashMap大致分为三个部分:散列表,红黑树算法,链表与树之间的转化。关于散列表部分,关于红黑树分析,这里就来分析下树化部分。
接下来先分析TreeNode 中的各个方法,之后再在相关操作中看它们的应用。
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);
}
TreeNode 继承自 LinkedHashMap.Entry,LinkedHashMap.Entry 继承自HashMap.Node,这绕了一圈是因为LinkedHashMap 的实现是依赖于HashMap的。关于LinkedHashMap的解析看我的这篇:LinkedHashMap源码解析(JDK8)
对于TreeNode 它有指向父节点的 parent,指向左节点的 left,右节点 right,前一个节点 prev,以及继承自 Node 的next,还有LinkedHashMap.Entry 中的 before,after指针,这两个与插入顺序的实现有关,在HashMap中并无对二者的任何相关操作。这就造成我们可以从两种不同视图角度来看树结构,一个是 parent,left,right 形成的二叉树,另一个是 next ,previous 形成的双向链表。
moveRootToFront:
就是将 root 前移至链头,不改变其在树中的位置。该方法是为了确保 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) {
int index = (n - 1) & root.hash;
TreeNode<K,V> first = (TreeNode<K,V>)tab[index];
if (root != first) {
Node<K,V> rn;
tab[index] = root;
TreeNode<K,V> rp = root.prev;
if ((rn = root.next) != null)
((TreeNode<K,V>)rn).prev = rp;
if (rp != null)
rp.next = rn;
if (first != null)
first.prev = root;
root.next = first;
root.prev = null;
}
assert checkInvariants(root);
}
}
treeify
在介绍 treeify 方法之前先来介绍下 TreeNode 节点之间的大小比较,假设要插入的节点 x,其关键字为 k,比较位置处节点 p,其关键字 pk :首先比较 x 与 p 的 hash字段(即 key 的hash),若相同则要看 k 的类是否是 class X implements Comparable<X>
这样的类型,是则调用 compareTo( pk ),前提是 pk 与 k 是同一类型(即 k.getClass == pk.getClass),否则则比较二者的类名,若仍相同则比较二者对象的hashCode,若 k 对象的hashCode 小于等于 pk 的,则返回 -1,代表 x 应插入到 p 的左子树中。
上述说明的实现对应 comparableClassFor,compareComparables,tieBreakOrder 三个方法:
comparableClassFor:
如果 x 类是这样的 :class X implements Comparable<X>,则返回 x 的class 对象,否则返回null。
static Class<?> comparableClassFor(Object x) {
if (x instanceof Comparable) {
// x必须实现Comparable接口
Class<?> c; Type[] ts, as; Type t; ParameterizedType p;
// String的化直接返回String.class
if ((c = x.getClass()) == String.class) // bypass checks
return c;
// 获取x直接实现的所有接口
if ((ts = c.getGenericInterfaces()) != null) {
// 遍历这些接口,找寻是否存在 Comparable<X>
for (int i = 0; i < ts.length; ++i) {
if (((t = ts[i]) instanceof ParameterizedType) &&
((p = (ParameterizedType)t).getRawType() ==
Comparable.class) &&
(as = p.getActualTypeArguments()) != null &&
as.length == 1 && as[0] == c) // type arg is c
return c;
}
}
}
return null;
}
关于 ParameterizedType 泛型参数化类型:ParameterizedType详解
compareComparables:
若 x 与 k 是同一个类,k 的类是这样的:class C implements Comparable<C>,
则调用 compareTo 方法进行比较,否则返回0。
static int compareComparables(Class<?> kc, Object k, Object x) {
return (x == null || x.getClass() != kc ? 0 :
((Comparable)k).compareTo(x));
}
tieBreakOrder
若 a ,b 对象其中有一个为null,或者是同一个类,则比较对象的hashCode;否则比较它们的类名。System.identityHashCode(null) 大小为 0。
static int tieBreakOrder(Object a, Object b) {
int d;
if (a == null || b == null ||
(d