jdk1.8HashMap的源码
树节点
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;
}
}
}
/*
* 入参root,根节点,x当前要插入的节点
*
*/
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
TreeNode<K,V> x) {
x.red = true;
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
if ((xp = x.parent) == null) {
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) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;
}
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;
}
if (xp != null) {
xp.red = false;
if (xpp != null) {
xpp.red = true;
root = rotateLeft(root, xpp);
}
}
}
}
}
}
在Jdk1.8版本后,Java对HashMap做了改进,在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度。
那么很多人就有疑问为什么是使用红黑树而不是AVL树,AVL树是完全平衡二叉树阿?
最主要的一点是:
在CurrentHashMap中是加锁了的,实际上是读写锁,如果写冲突就会等待,如果插入时间过长必然等待时间更长,而红黑树相对AVL树他的插入更快!
第一个问题为什么不一直使用树?
我想这是内存占用与存储桶内查找复杂性之间的权衡。请记住,大多数哈希函数将产生非常少的冲突,因此为大小为3或4的桶维护树将是非常昂贵的,没有充分的理由。
作为参考,这是一个HashMap的Java 8 impl(它实际上有一个很好的解释,整个事情如何工作,以及为什么他们选择8和6,作为“TREEIFY”和“UNTREEIFY”阈值)
第二个问题为什么hash冲突使用红黑树而不是AVL树呢
红黑树和AVL树之间的区别
-
AVL树比红黑树保持更加严格的平衡。AVL树中从根到最深叶的路径最多为~1.44 lg(n + 2),而在红黑树中最多为~2 lg(n + 1)。
因此,在AVL树中查找通常更快,但这是以更多旋转操作导致更慢的插入和删除为代价的。因此,如果您希望查找次数主导树的更新次数,请使用AVL树。 -
AVL以及RedBlack树是高度平衡的树数据结构。它们非常相似,真正的区别在于在任何添加/删除操作时完成的旋转操作次数。
两种实现都缩放为a O(lg N),其中N是叶子的数量,但实际上AVL树在查找密集型任务上更快:利用更好的平衡,树遍历平均更短。另一方面,插入和删除方面,AVL树速度较慢:需要更高的旋转次数才能在修改时正确地重新平衡数据结构。 -
对于通用实现(即先验并不清楚查找是否是操作的主要部分),RedBlack树是首选:它们更容易实现,并且在常见情况下更快 - 无论数据结构如何经常被搜索修改。一个例子,TreeMap而TreeSet在Java中使用一个支持RedBlack树。
对于小数据:
-
insert:RB tree&avl tree具有恒定的最大旋转次数,但RB树会更快,因为平均RB树使用较少的旋转。
-
查找:AVL树更快,因为AVL树的深度较小。
-
删除:RB树具有恒定的最大旋转次数,但AVL树可以将O(log N)次旋转视为最差。并且平均而言,RB树也具有较少的旋转次数,因此RB树更快。
对于大数据:
-
insert:AVL树更快。因为您需要在插入之前查找特定节点。当您有更多数据时,查找特定节点的时间差异与O(log N)成比例增长。但在最坏的情况下,AVL树和RB树仍然只需要恒定的旋转次数。因此,瓶颈将成为您查找该特定节点的时间。
-
查找:AVL树更快。(与小数据情况相同)
-
删除:AVL树平均速度更快,但在最坏的情况下,RB树更快。因为您还需要在删除之前查找非常深的节点以进行交换(类似于插入的原因)。平均而言,两棵树都有恒定的旋转次数。但RB树有一个恒定的旋转上限。