前言
上篇文章中介绍了treeifyBin方法,我们找到了另一个引起HashMap扩容的原因。这次我们一起来看下treeifyBin方法除了扩容之外,还能做什么事。
正文
1、设计目的
设计一个测试案例,是为了探寻研究treeifyBin方法在HashMap实例的节点数组长度超过64时的行为。
2、设计思路
定义一个HashMap,使其满足以下条件:
(1)节点数组长度超过64;
(2)制造一个长度超过8的链表;
通过调试观察treeifyBin方法的行为。
3、设计测试案例
(1)满足条件1
为了使节点数组长度超过64,测试案例初步拟定如下:
Map<String,Object> map = new HashMap<>();
for (int i =0;i<64;i++){
map.put(((Integer) i).toString(),9);
}
System.out.println("map.size:"+map.size());
(2)满足条件2
首先,利用HashMap的hash方法来寻找链表所需的key,代码如下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
@Test
public void testHash(){
int n = 128;
String s = "ssm";
int hash = hash(s);
System.out.println(hash);
int res = (n - 1) & hash;
System.out.println(res);
s = "hej";
hash = hash(s);
System.out.println(hash);
res = (n - 1) & hash;
System.out.println(res);
s = "eck";
hash = hash(s);
System.out.println(hash);
res = (n - 1) & hash;
System.out.println(res);
s = "qso";
hash = hash(s);
System.out.println(hash);
res = (n - 1) & hash;
System.out.println(res);
s = "hderm";
hash = hash(s);
System.out.println(hash);
res = (n - 1) & hash;
System.out.println(res);
s = "hderni";
hash = hash(s);
System.out.println(hash);
res = (n - 1) & hash;
System.out.println(res);
s = "eow";
hash = hash(s);
System.out.println(hash);
res = (n - 1) & hash;
System.out.println(res);
s = "xdy";
hash = hash(s);
System.out.println(hash);
res = (n - 1) & hash;
System.out.println(res);
s = "lyz";
hash = hash(s);
System.out.println(hash);
res = (n - 1) & hash;
System.out.println(res);
}
控制台输出如截图所示:
(3)完成测试案例
有了链表所需的key值后,我们完成的测试案例如下:
@Test
public void test2(){
Map<String,Object> map = new HashMap<>();
for (int i =0;i<64;i++){
map.put(((Integer) i).toString(),9);
}
System.out.println("map.size:"+map.size());
map.put("ssm",4);
map.put("hej",4);
map.put("eck",4);
map.put("qso",4);
map.put("hderm",4);
map.put("hderni",4);
map.put("eow",4);
map.put("xdy",4);
map.put("lyz",4);
System.out.println(map.size());
}
4、调试分析
通过调试发现,HashMap如预期一样组成了一条长度为9的链表,成功进入treeifyBin方法:
/**
* Replaces all linked nodes in bin at index for given hash unless
* table is too small, in which case resizes instead.
*/
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);
}
}
此时tab的长度为128, (n - 1) & hash的运算结果为12,满足了(e = tab[index = (n - 1) & hash]) != null判断条件。
这里的代码就是我们关注的重点:
首先,定义了两个初始值为null的TreeNode对象:
TreeNode<K,V> hd = null, tl = null;
接着,进入了一个do-while代码块:
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);
由于do-while语句的特点,这里会先调用replacementTreeNode方法:
// For treeifyBin
TreeNode<K,V> replacementTreeNode(Node<K,V> p, Node<K,V> next) {
return new TreeNode<>(p.hash, p.key, p.value, next);
}
可以看到,replacementTreeNode方法新构建了一个TreeNode对象并返回。
我们来看下TreeNode:
/**
* Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
* extends Node) so can be used as extension of either regular or
* linked node.
*/
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);
}
/**
* Returns root of tree containing this node.
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
/**
* Ensures that the given root is the first node of its bin.
*/
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);
}
}
从TreeNode内部成员属性可以看出,它是一个红黑树结构。TreeNode<K,V>继承自 LinkedHashMap.Entry<K,V>,而 LinkedHashMap.Entry<K,V>又继承自HashMap.Node<K,V>,所以TreeNode<K,V>可以作为Node<K,V>来存储节点数据。
回到do-while代码块中,我们最终得到了一条加强版的“链表”,链表上的节点顺序没变,但是变成了双向链表,也就是说我们可以通过已知的链表末端节点来逆向找到链表上的其他节点。
最后,HashMap将这条双向链表放入了当前索引位置,并调用了treeify方法:
if ((tab[index] = hd) != null)
hd.treeify(tab);
我们看下treeify方法:
/**
* Forms tree of the nodes linked from this node.
* @return root of tree
*/
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);
}
这里将双向链表转化成了一棵红黑树。
下一篇我们研究下HashMap如何将双向链表转化成红黑树的。