由 java 8 HashMap 中的红黑树 ,引起出对2叉树、 B树、B+树 的 分析

前些天在网上偶然 间看到一篇关于java 8的  HashMap的分析文章,其对java7进行了大量改进,核心是引进了红黑树,提升了hashcode碰撞严重时的查找性能。这一点引起了我对红黑树的兴趣。小白的我表示以前对2叉树也是一之半解,更不要提复杂的红黑树了。所以下定决心分析下红黑树。html

首先介绍几个我在这周过程当中在网上看到的一些比较好的文章以下:java

关于红黑树:算法

http://www.kuqin.com/shuoit/20160630/352539.html   对插入和删除分析的比较到位数组

http://blog.csdn.net/v_july_v/article/details/6105630    比较详细的介绍了红黑树的相关知数据结构

http://blog.csdn.net/v_JULY_v/article/details/6284050   july的系列文章,并发

http://www.cs.usfca.edu/~galles/visualization/Algorithms.html   算法的动态过程ide

 

java  中的数据结构分析HashMap  TreeMap性能

http://tech.meituan.com/java-hashmap.html   美团 分析 的从新认识HashMapthis

http://www.importnew.com/21818.html   TreeMap 实现spa

https://www.ibm.com/developerworks/cn/java/j-lo-tree/   TreeMap

http://yemengying.com/2016/05/07/threadsafe-hashmap/  HashMap并发性问题

接下来,对我这周的分析进行总结:

红黑树 

首先介绍下红黑树的相关知识,其是一颗平衡的二叉树。其在二叉查找树的基础上增长了红黑色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。

具体的5个性质简单要介绍,1)结点要么为黑要么为红、2)根结点为黑色、3)每一个叶节点(包括null节点)为黑色、4)红色结点的叶结点必须为黑色、5)全部的节点到叶节点的黑结点数相同。

从上面五个性质,咱们能够得出以下 结点能够是连续黑色的,n个结点的高度为logN。

红黑树的插入和删除是经过结点的旋转(左右旋转),节点变色来从新平衡。接下来重点介绍下插入的恢复过程:

  插入必为红色,其叔叔为红色,将叔,父变黑,祖父变红。叔叔为黑色,根据其父是左结点仍是右结点,进行相应的左旋与右旋。然后将父节点变黑,祖父变红。如此直到根结点。

 

TreeMap 与HashMap

以前 我就在想,hashMap若是用到了红黑树,能够直接引用treemap中的红黑树算法,但实际是HashMap中也本身实现了一套HashMap。首先分析下treeMap的算法实现:

    TreeMap在构造有必定要求,若是在构造时没有比较器。则在put时,若是key没有实现comparable接口,则会抛出异常。所以要么在构造时指定,要么key实现comparable接口。

TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}

接着就是经过一个标准的2叉树查找实现,找到插入点。

Entry<K,V> t = root;
do {
    parent = t;
    cmp = cpr.compare(key, t.key);
    if (cmp < 0)
        t = t.left;
    else if (cmp > 0)
        t = t.right;
    else
        return t.setValue(value);
} while (t != null);

查到插入点parent 后,插入节点,然后经过fixAfterInsertion(e)自修复

Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
    parent.left = e;
else
    parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;

修复过程以下:

首先将节点变为红色,

while (x != null && x != root && x.parent.color == RED) {
    if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
        Entry<K,V> y = rightOf(parentOf(parentOf(x)));
        if (colorOf(y) == RED) {
            setColor(parentOf(x), BLACK);
            setColor(y, BLACK);
            setColor(parentOf(parentOf(x)), RED);
            x = parentOf(parentOf(x));
        } else {
            if (x == rightOf(parentOf(x))) {
                x = parentOf(x);
                rotateLeft(x);
            }
            setColor(parentOf(x), BLACK);
            setColor(parentOf(parentOf(x)), RED);
            rotateRight(parentOf(parentOf(x)));
        }
    } else {
       与上面相似,只是方向不一样,这里的逻辑对应的是父节点是祖父节点的右孩子,
       因此若是当前节点是左孩子,先要右旋。造成两个连续的红色,接着祖父变红,父变黑,由于以前是右孩子,左旋。
    }}
    root.color = Baclk;
}

最后当前结点为root时,将root结点改成Black,插入结束。

删除时相似,先找到节点,删除后恢复,相似。

HashMap

HashMap 在1.8 以前 ,由一个链表的数组。不是一个单纯的树。而java8 后,一样仍是数组,可是数组中的值有了变化,并必定是链表,默认的设置当前超过8时,链表转换成红黑树的结构,可是仍是会保持一个以红黑树的root的节点为链表头的链表。

其中treeMap中与另一个与TreeMap中不一样的是,key不要求实现comparable,或者指定比较器,而是用hashcode值 的大小来比较,注意不是hashcode的 转换后,比数据长度&后的 值 ,由于同一个链表中的该值是相等的,而hashcode不必定同样,可是若是 相等,则会经过 object对象的,以下方法,若是没有重写hashcode,则默认用以下方法

System.identityHashCode(a)

咱们重点介绍下数据节点是红黑树的插入状况,

调用put 方法时,先将hashcode的高16位与低16位异或,让高位也参与 位置的计算,减少hash冲突,然后与数组的大小进去位与运算,而数组的大小是2的幂,这样的目的是为了在扩容时,原来在在同一个数组的位置,要么在原来的位置,要么在原来位置的两倍。减少hash运算。

接着若是计算出来的位置的节点 当前是红黑树,则就是红黑树的插入过程,可是不一样的时,须要将计算出来的root节点,移动到链表的头部。

而若是大小等于8时,就须要将链表转换成结点,先要将每一个结点转换成红黑树节点 ,然后依次插入,造成红黑树。删除8时,须要从新转换为链表。

还有一个与java7 最大的不一样就是 resize的过程。 java7 的实现 方式,从新遍历每一个节点,从新计算插入扩容后的数组中,而java8 是没有从新计算hashcode,而是将原hashcode后的hash值 与扩容后的那个增长位进行与运算,决定 数组的位置。

 

先介绍到这里,后面在补充。

相关文章
相关标签/搜索