Hashmap底层实现原理(JDK1.8)
jdk1.7Hashmap的实现原理的博客我之前已经写过了,下面这个是链接地址:
jdk1.7HashMap的实现原理
在说jdk1.8的HashMap之前呢,我们先了解一下AVL树和红黑树:
在这里先推荐一个数据结构可视化软件:
Data Structure Visualization
AVL数:
在计算机科学中,AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。
平衡二叉树递归定义如下:
左右子树的高度差小于等于 1。
其每一个子树均为平衡二叉树。
基于这一句话,我们就可以进行判断其一棵树是否为平衡二叉树了。
当我们插入一个元素3是,本来3应该插入在2的右子树上面,但是这样就会不满足平衡二叉树的性质,所以会发生左旋,变成这样:
当我们插入一个元素时,1应该在2的左子树上面,但是这时就不满足平衡二叉树的性质了,所以要发生右旋,变成这样:
AVL数在增加和删除操作可能需要通过一次或多次树旋转来重新平衡这个树,以保证平衡二叉树的性质.
红黑树
红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。
它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除.
<算法导论>中对红黑树的定义:
- 每个节点或是红的,或是黑的;
- 根节点是黑的;
- 每个叶节点是黑的;
- 如果一个节点是红的,则它的儿子节点都是黑的;(父子节点不能出现两个连续的红节点)
- 对每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点.
我们看到的5,20,40.60它们其实不是叶子节点
它们下面其实默认是有空节点的,所以它们都不是叶子节点.那些空节点才是叶子节点(对定义3的一种解释).
插入节点:
对红黑树的实现方式来说,插入一个新节点,这个节点的颜色默认是红色的.
比如现在有一个红黑树:
当我们插入一个65的时候,我们要插在60的右节点上面,如果我们直接插入黑色节点,那么就不满足红黑树的第5个定义了**(对每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点)**,因此默认插入的是一个红节点.当然也可能这样会导致满足不了其他的定义,但是至少我们满足了第5个定义(最复杂).
让我们来一些插入的流程:
1.先插入一个为10的节点:
2.再插入一个20
因为20比10大,因此要插入到10的右子树;
3,再插入一个30
因为30比10大,所以找10的右子树20.30也比20大,所以30插入在20的右子树;然后判断并进行相应的操作(左旋加变色)
4.再插入一个40
40要插入在30的右子树;然后进行操作(变色)
5.插入5和25
6.插入50
我们可以分析一下一下:
1.如果新节点的父节点是黑色的,则不用变色(如操作5,插入5和25的情况)
2.新节点的父节点( P )是红色的, 叔叔是空的,这个时候我们要进行旋转+变色(如操作3,插入30的情况)
3.新节点的父节点是红色的,叔叔是红色的,这个时候我们要把父节点+叔叔变黑色,祖父节点变为红色.(如操作6,插入一个50).
4.新节点的父节点是红色的,叔叔是黑色的,这个时候我们要进行旋转+变色
这个情况其实是存在的:
比如我们给下面这个红黑树添加一个60的节点
是不是通过第三个规律(新节点的父节点是红色的,叔叔是红色的: 父节点+叔叔变黑色,祖父节点变为红色)我们可以变成这样:
但是这时我们就会发现30节点和40节点的节点颜色都是红色,不满足红黑树的定义,然后我们可以发现50节点的父节点是红色的,叔叔节点是黑色的,这不正好是第四种情况吗?这时我们通过旋转+变色可以将它们调整为红黑树.
总结一下就是:
插入的新节点开始都是红色的;
当插入的节点的父节点是黑色的,不用在进行操作;
当插入的节点的父节点是红色的:
1.插入节点的叔叔是空的, 那么要进行旋转+变色;(G和P都变色,且G成为P的子节点);
2.叔叔是红色的, 那么要将叔叔节点和父节点都染成黑色,祖父节点要变成红色;
3,叔叔是黑色的,那么要进行旋转+变色.
当然还有一种比较特殊情况:
当我们给上面那个红黑树插入4节点时,这个操作很容易:
可是如果插入一个6呢? 如果按照之前的方式去做得到的这个树不满足红黑树的定义.
因此我们要先把要插入的节点与父节点调换位置(通过左旋的方式实现),这时通过旋转(右旋)+变色就能的到一个正确的红黑树了.
这个是JKD1.8HashMap源码里面红黑树的插入代码:
转化为红黑树的节点类:
//转化为红黑树的节点类
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
//当前节点的父节点
TreeNode<K,V> parent;
//左孩子节点
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);
}
}
这里是将插入的节点插入到相对应的位置,然后调用balanceInsertion()方法,对红黑树进行一些操作(旋转,染色).
然后来看看balanceInsertion()方法
//此时节点已经插入的红黑树正确的位置了,但是还没有进行其他的操作
//root表示当前红黑树的根节点,x表示要插入红黑树的节点
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,TreeNode<K,V> x) {
//新节点默认为红色
x.red = true;
//xp表示x的父节点,xpp表示x的祖父节点,sppl表示xpp的左孩子节点,xppr表示xpp的右孩子节点.
for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
//如果xp即x的父节点为空,则表示x是第一个节点,自动为根节点,根节点为黑色;
if ((xp = x.parent) == null) {
x.red = false;
return x;
}
//如果xp即x的父节点不是红色 或是 x的祖父节点为空,即x的父节点为根节点,直接返回
else if (!xp.red || (xpp = xp.parent) == null)
return root;
/*
接下来的if情况都是x的父节点为红色的情况:
*/
//如果xp即x的父节点是x的祖父的左节点
if (xp == (xppl = xpp.left)) {
//父节点为红色,并且叔叔节点为红色的情况(叔叔节点和父节点都染成黑色,祖父节点要变成红色)
if ((xppr = xpp.right) != null && xppr.red) {
xppr.red = false; //叔叔红色变为黑色
xp.red = false; //父节点红色也变为黑色
xpp.red = true; //祖父节点变红色
x = xpp; //给x赋值为x的祖父节点,即这颗小树的根节点,继续进行递归,将整棵树都调整好.
}
//x的叔叔节点为null或者叔叔节点为黑色的情况(旋转+变色)
else {
//这个if就是上面说的特殊情况,但是这个if情况只进行了第一步(左旋)
if (x == xp.right) {
root = rotateLeft(root, x = xp);
xpp = (xp = x.parent) == null ? null : xp.parent;//左旋结束后,重新对xp赋值
}
//先变色,在右旋
if (xp != null) {
xp.red = false; //变色
if (xpp != null) {
xpp.red = true;
root = rotateRight(root, xpp); //右旋
}
}
}
}
//x的父节点是xpp(x的祖父节点)的右节点(和上面情况同理)
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);
}
}
}
}
}
}
左旋rotateLeft()方法:
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> r, pp, rl;
if (p != null && (r = p.right) != null) {
if ((rl = p.right = r.left) != null)
rl.parent = p;
if ((pp = r.parent = p.parent) == null)
(root = r).red = false;
else if (pp.left == p)
pp.left = r;
else
pp.right = r;
r.left = p;
p.parent = r;
}
return root;
}
右旋rotateRight()方法;
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
TreeNode<K,V> p) {
TreeNode<K,V> l, pp, lr;
if (p != null && (l = p.left) != null) {
if ((lr = p.left = l.right) != null)
lr.parent = p;
if ((pp = l.parent = p.parent) == null)
(root = l).red = false;
else if (pp.right == p)
pp.right = l;
else
pp.left = l;
l.right = p;
p.parent = l;
}
return root;
}
这里基本上红黑树的增加节点的原理差不多就结束了.