TreeMap源码分析(jdk1.8)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_23211905/article/details/76691961

TreeMap的基本概念:

  1. TreeMap集合是基于红黑树(Red-Black tree)的 NavigableMap实现。该集合最重要的特点就是可排序,该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。这句话是什么意思呢?就是说TreeMap可以对添加进来的元素进行排序,可以按照默认的排序方式,也可以自己指定排序方式。
  2. 根据上一条,我们要想使用TreeMap存储并排序我们自定义的类(如User类),那么必须自己定义比较机制:一种方式是User类去实现Java.lang.Comparable接口,并实现其compareTo()方法。另一种方式是写一个类(如MyCompatator)去实现java.util.Comparator接口,并实现compare()方法,然后将MyCompatator类实例对象作为TreeMap的构造方法参数进行传参。
  3. TreeMap的实现是红黑树算法的实现,应该了解红黑树的基本概念。
    一、红黑树简介
    红黑树又称红-黑二叉树,它首先是一颗二叉树,它具体二叉树所有的特性。同时红黑树更是一颗自平衡的排序二叉树。
    1、每个节点都只能是红色或者黑色
    2、根节点是黑色
    3、每个叶节点(NIL节点,空节点)是黑色的。
    4、如果一个结点是红的,则它两个子节点都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
    5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
    具体实现参考:http://www.cnblogs.com/fanzhidongyzby/p/3187912.html
    http://blog.csdn.net/eric491179912/article/details/6179908

/************************************TreeMap********************************/
TreeMap的定义如下:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

TreeMap继承AbstractMap,实现NavigableMap、Cloneable、Serializable三个接口。其中AbstractMap表明TreeMap为一个Map即支持key-value的集合, NavigableMap(更多)则意味着它支持一系列的导航方法,具备针对给定搜索目标返回最接近匹配项的导航方法 。
TreeMap中同时也包含了如下几个重要的属性:

//比较器,因为TreeMap是有序的,通过comparator接口我们可以对TreeMap的内部排序进行精密的控制
        private final Comparator<? super K> comparator;
        //TreeMap红-黑节点,为TreeMap的内部类
        private transient Entry<K,V> root = null;
        //容器大小
        private transient int size = 0;
        //TreeMap修改次数
        private transient int modCount = 0;
        //红黑树的节点颜色--红色
        private static final boolean RED = false;
        //红黑树的节点颜色--黑色
        private static final boolean BLACK = true;
       // 静态内部类用来表示节点类型
    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;     // 键
        V value;   // 值
        Entry<K,V> left;    // 指向左子树的引用(指针)
        Entry<K,V> right;   // 指向右子树的引用(指针)
        Entry<K,V> parent;  // 指向父节点的引用(指针)
        boolean color = BLACK; // 
    }
}

类构造方法

    public TreeMap() {   // 1,无参构造方法
        comparator = null; // 默认比较机制
    }

    public TreeMap(Comparator<? super K> comparator) { // 2,自定义比较器的构造方法
        this.comparator = comparator;
    }

    public TreeMap(Map<? extends K, ? extends V> m) {  // 3,构造已知Map对象为TreeMap
        comparator = null; // 默认比较机制
        putAll(m);
    }

    public TreeMap(SortedMap<K, ? extends V> m) { // 4,构造已知的SortedMap对象为TreeMap
        comparator = m.comparator(); // 使用已知对象的构造器
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }

TreeMap put()方法实现分析
在TreeMap的put()的实现方法中主要分为两个步骤,第一:构建排序二叉树,第二:平衡二叉树。
对于排序二叉树的创建,其添加节点的过程如下:

  1. 以根节点为初始节点进行检索。
  2. 与当前节点进行比对,若新增节点值较大,则以当前节点的右子节点作为新的当前节点。否则以当前节点的左子节点作为新的当前节点。
  3. 循环递归2步骤知道检索出合适的叶子节点为止。
  4. 将新增节点与3步骤中找到的节点进行比对,如果新增节点较大,则添加为右子节点;否则添加为左子节点。
public V put(K key, V value) {  
           //用t表示二叉树的当前节点  
            Entry<K,V> t = root;  
            //t为null表示一个空树,即TreeMap中没有任何元素,直接插入  
            if (t == null) {  
                //比较key值,空树还需要比较、排序?
                compare(key, key); // type (and possibly null) check  
                //将新的key-value键值对创建为一个Entry节点,并将该节点赋予给root  
                root = new Entry<>(key, value, null);  
                //容器的size = 1,表示TreeMap集合中存在一个元素  
                size = 1;  
                //修改次数 + 1  
                modCount++;  
                return null;  
            }  
            int cmp;     //cmp表示key排序的返回结果  
            Entry<K,V> parent;   //父节点  
            // split comparator and comparable paths  
            Comparator<? super K> cpr = comparator;    //指定的排序算法  
            //如果cpr不为空,则采用既定的排序算法进行创建TreeMap集合  
            if (cpr != null) {  
                do {  
                    parent = t;      //parent指向上次循环后的t  
                    //比较新增节点的key和当前节点key的大小  
                    cmp = cpr.compare(key, t.key);  
                    //cmp返回值小于0,表示新增节点的key小于当前节点的key,则以当前节点的左子节点作为新的当前节点  
                    if (cmp < 0)  
                        t = t.left;  
                    //cmp返回值大于0,表示新增节点的key大于当前节点的key,则以当前节点的右子节点作为新的当前节点  
                    else if (cmp > 0)  
                        t = t.right;  
                    //cmp返回值等于0,表示两个key值相等,则新值覆盖旧值,并返回新值  
                    else  
                        return t.setValue(value);  
                } while (t != null);  
            }  
            //如果cpr为空,则采用默认的排序算法进行创建TreeMap集合  
            else {  
                if (key == null)     //key值为空抛出异常  
                    throw new NullPointerException();  
                /* 下面处理过程和上面一样 */  
                Comparable<? super K> k = (Comparable<? super K>) key;  
                do {  
                    parent = t;  
                    cmp = k.compareTo(t.key);  
                    if (cmp < 0)  
                        t = t.left;  
                    else if (cmp > 0)  
                        t = t.right;  
                    else  
                        return t.setValue(value);  
                } while (t != null);  
            }  
            //将新增节点当做parent的子节点  
            Entry<K,V> e = new Entry<>(key, value, parent);  
            //如果新增节点的key小于parent的key,则当做左子节点  
            if (cmp < 0)  
                parent.left = e;  
          //如果新增节点的key大于parent的key,则当做右子节点  
            else  
                parent.right = e;  
            /*  
             *  上面已经完成了排序二叉树的的构建,将新增节点插入该树中的合适位置  
             *  下面fixAfterInsertion()方法就是对这棵树进行调整、平衡,具体过程参考上面的五种情况  
             */  
            fixAfterInsertion(e);  
            //TreeMap元素数量 + 1  
            size++;  
            //TreeMap容器修改次数 + 1  
            modCount++;  
            return null;  
        }  

上面代码中do{}代码块是实现排序二叉树的核心算法,通过该算法我们可以确认新增节点在该树的正确位置。找到正确位置后将插入即可,这样做了其实还没有完成,因为我知道TreeMap的底层实现是红黑树,红黑树是一棵平衡排序二叉树,普通的排序二叉树可能会出现失衡的情况,所以下一步就是要进行调整。fixAfterInsertion(e); 调整的过程务必会涉及到红黑树的左旋、右旋、着色三个基本操作。代码如下:

/** 
     * 新增节点后的修复操作 
     * x 表示新增节点 
     */  
     private void fixAfterInsertion(Entry<K,V> x) {  
            x.color = RED;    //新增节点的颜色为红色  

            //循环 直到 x不是根节点,且x的父节点不为红色  
            while (x != null && x != root && x.parent.color == RED) {  
                //如果X的父节点(P)是其父节点的父节点(G)的左节点  
                if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {  
                    //获取X的叔节点(U)  
                    Entry<K,V> y = rightOf(parentOf(parentOf(x)));  
                    //如果X的叔节点(U) 为红色(情况三)  
                    if (colorOf(y) == RED) {       
                        //将X的父节点(P)设置为黑色  
                        setColor(parentOf(x), BLACK);  
                        //将X的叔节点(U)设置为黑色  
                        setColor(y, BLACK);  
                        //将X的父节点的父节点(G)设置红色  
                        setColor(parentOf(parentOf(x)), RED);  
                        x = parentOf(parentOf(x));  
                    }  
                    //如果X的叔节点(U为黑色);这里会存在两种情况(情况四、情况五)  
                    else {     
                        //如果X节点为其父节点(P)的右子树,则进行左旋转(情况四)  
                        if (x == rightOf(parentOf(x))) {  
                            //将X的父节点作为X  
                            x = parentOf(x);  
                            //右旋转  
                            rotateLeft(x);  
                        }  
                        //(情况五)  
                        //将X的父节点(P)设置为黑色  
                        setColor(parentOf(x), BLACK);  
                        //将X的父节点的父节点(G)设置红色  
                        setColor(parentOf(parentOf(x)), RED);  
                        //以X的父节点的父节点(G)为中心右旋转  
                        rotateRight(parentOf(parentOf(x)));  
                    }  
                }  
                //如果X的父节点(P)是其父节点的父节点(G)的右节点  
                else {  
                    //获取X的叔节点(U)  
                    Entry<K,V> y = leftOf(parentOf(parentOf(x)));  
                  //如果X的叔节点(U) 为红色(情况三)  
                    if (colorOf(y) == RED) {  
                        //将X的父节点(P)设置为黑色  
                        setColor(parentOf(x), BLACK);  
                        //将X的叔节点(U)设置为黑色  
                        setColor(y, BLACK);  
                        //将X的父节点的父节点(G)设置红色  
                        setColor(parentOf(parentOf(x)), RED);  
                        x = parentOf(parentOf(x));  
                    }  
                  //如果X的叔节点(U为黑色);这里会存在两种情况(情况四、情况五)  
                    else {  
                        //如果X节点为其父节点(P)的右子树,则进行左旋转(情况四)  
                        if (x == leftOf(parentOf(x))) {  
                            //将X的父节点作为X  
                            x = parentOf(x);  
                           //右旋转  
                            rotateRight(x);  
                        }  
                        //(情况五)  
                        //将X的父节点(P)设置为黑色  
                        setColor(parentOf(x), BLACK);  
                        //将X的父节点的父节点(G)设置红色  
                        setColor(parentOf(parentOf(x)), RED);  
                        //以X的父节点的父节点(G)为中心右旋转  
                        rotateLeft(parentOf(parentOf(x)));  
                    }  
                }  
            }  
            //将根节点G强制设置为黑色  
            root.color = BLACK;  
        }  

TreeMap delete()方法:针对于红黑树的增加节点而言,删除显得更加复杂,使原本就复杂的红黑树变得更加复杂。同时删除节点和增加节点一样,同样是找到删除的节点,删除之后调整红黑树。但是这里的删除节点并不是直接删除,而是通过走了“弯路”通过一种捷径来删除的:找到被删除的节点D的子节点C,用C来替代D,不是直接删除D,因为D被C替代了,直接删除C即可。所以这里就将删除父节点D的事情转变为了删除子节点C的事情,这样处理就将复杂的删除事件简单化了。子节点C的规则是:右分支最左边,或者 左分支最右边的。
一帮以getEntry()方法为基础的获取元素的方法,其中包括containsKey(),get(),remove()等。

final Entry<K,V> getEntry(Object key) {
        // 如果有自定义比较器对象,就按照自定义规则遍历二叉树
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
            Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;
        while (p != null) {    // 按照默认比较规则遍历二叉树
            int cmp = k.compareTo(p.key);
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }

一帮以getFirstEntry(),getLastEntry()为基础的获取头和尾元素的方法,其中包括:firstKey(),lastKey();firstEntry(),lastEntry();pollFirstEntry(),pollLastEntry()

    final Entry<K,V> getFirstEntry() { // 获取第一个元素也就是最小的元素,一直遍历左子树
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

    final Entry<K,V> getLastEntry() { // 获取最后个元素也就是最大的元素,一直遍历右子树
        Entry<K,V> p = root;
        if (p != null)
            while (p.right != null)
                p = p.right;
        return p;
    }

treeMap只能根据key值进行排序,根据value进行排序可以参考下面方法:
http://blog.csdn.net/qq_23211905/article/details/72627046

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页