zoukankan      html  css  js  c++  java
  • 给jdk写注释系列之jdk1.6容器(8)-TreeSet&NavigableMap&NavigableSet源码解析

      TreeSet是一个有序的Set集合。
      既然是有序,那么它是靠什么来维持顺序的呢,回忆一下TreeMap中是怎么比较两个key大小的,是通过一个比较器Comparator对不对,不过遗憾的是,今天仍然不会讲Comparator,但是需要明白的是TreeSet要实现信息也必须依靠于Comparator接口。
         关于Set,在前面我们讲过一个HashSet,是不是想起了什么,Set和Map在java中是很神奇的一对东东,是的,是一对,他们都是一对对出现的,就像双胞胎。来看一下这两个容器(是的,容器,我们还是要正规一些,什么双胞胎嘛),Map有HashMap,LinkedHashMap还有TreeMap,那Set呢有HashSet,LinkedHashSet还有TreeSet,很一致是不是。还有一点就是,所有的Set的实现都是依靠于Map的,这一点在HashSet中有讲过,重复一篇Set的实现是利用Map作为底层存储,主要用到Map的key来存储元素。不要问我为什么,也不要问我Set为什么不独立一些。
         好了,我们知道了TreeSet和TreeMap一样都是基于红黑树实现,明白了前面的TreeMap原理,TreeSet我都不打算说了。
         
    1.定义
    1 public class TreeSet<E> extends AbstractSet<E>
    2     implements NavigableSet<E>, Cloneable, java.io.Serializable
      从定义上可以看出TreeSet继承了AbstractSet抽象类,并实现了NavigableSet、Cloneable,Serializable接口,对于NavigableSet是不是还有些许印象,在TreeMap中出现过一个NavigableMap,它们的的目的都一样,都是为了提供跟搜索相关的接口,具体怎么实现,我们后面看。
     
         不过要先看下NavigableSet的接口定义:
     1 public interface NavigableSet<E> extends SortedSet<E> {
     2     E lower(E e);
     3     E floor(E e);
     4     E ceiling(E e);
     5     E higher(E e);
     6     E pollFirst();
     7     E pollLast();
     8     Iterator<E> iterator();
     9     NavigableSet<E> descendingSet();
    10     Iterator<E> descendingIterator();
    11     NavigableSet<E> subSet(E fromElement, boolean fromInclusive,
    12                            E toElement,   boolean toInclusive);
    13     NavigableSet<E> headSet(E toElement, boolean inclusive);
    14     NavigableSet<E> tailSet(E fromElement, boolean inclusive);
    15     SortedSet<E> subSet(E fromElement, E toElement);
    16     SortedSet<E> headSet(E toElement);
    17     SortedSet<E> tailSet(E fromElement);
    18 }
      我把注释都给删掉了,如果只看接口定义,详细你和我一样看不懂这些接口都是什么意思,不着急,我们下面会细讲。
     
    2.底层存储和构造方法
     
    1     // 底层使用NavigableMap来保存TreeSet的元素
    2     private transient NavigableMap<E,Object> m;
    3 
    4     // Dummy value to associate with an Object in the backing Map
    5     // 由于Set只使用到了Map的key,所以此处定义一个静态的常量Object类,来充当Map的value
    6     private static final Object PRESENT = new Object();

      我想,对于PRESENT这个常量不用多解释了吧,在HashSet中解释过的。至于这里的NavigableMap是什么东西,下面说。

     1     /**
     2      * 使用指定的navigable map来构造TreeSet
     3      */
     4     TreeSet(NavigableMap<E,Object> m) {
     5         this.m = m;
     6     }
     7 
     8     /**
     9      * 默认构造方法,底层使用TreeMap来存储TreeSet元素
    10      */
    11     public TreeSet() {
    12         this(new TreeMap<E,Object>());
    13     }
    14 
    15     /**
    16      * 使用指定的构造器,构造一个TreeMap来保存TreeSet的数据
    17      */
    18     public TreeSet(Comparator<? super E> comparator) {
    19         this(new TreeMap<E,Object>(comparator));
    20     }
    21 
    22     /**
    23      * 构造一个指定Collection参数的TreeSet
    24      */
    25     public TreeSet(Collection<? extends E> c) {
    26         this();
    27         addAll(c);
    28     }
    29 
    30     /**
    31      * 构造一个指定SortedMap的TreeSet,根据SortedMap的比较器来来维持TreeSet的顺序
    32      */
    33     public TreeSet(SortedSet<E> s) {
    34         this(s.comparator());
    35        addAll(s);
    36     }
      
      有么有很奇怪TreeSet底层用的是NavigableMap来存储数据,而不是直接使用TreeMap,我们知道TreeMap是实现类NavigableMap接口的,所以TreeSet默认构造了一个TreeMap来作为NavigableMap的一个实现类,提供给TreeSet存储数据。那么NavigableMap到底是什么东东呢?
         NavigableMap定义:
     1 public interface NavigableMap<K,V> extends SortedMap<K,V> {
     2     // 获取小于指定key的第一个节点对象
     3     Map.Entry<K,V> lowerEntry(K key);
     4 
     5     // 获取小于指定key的第一个key
     6     K lowerKey(K key);
     7 
     8     // 获取小于或等于指定key的第一个节点对象
     9     Map.Entry<K,V> floorEntry(K key);
    10 
    11     // 获取小于或等于指定key的第一个key
    12     K floorKey(K key);
    13 
    14     // 获取大于或等于指定key的第一个节点对象
    15     Map.Entry<K,V> ceilingEntry(K key);
    16 
    17     // 获取大于或等于指定key的第一个key
    18     K ceilingKey(K key);
    19 
    20     // 获取大于指定key的第一个节点对象
    21     Map.Entry<K,V> higherEntry(K key);
    22 
    23     // 获取大于指定key的第一个key
    24     K higherKey(K key);
    25 
    26     // 获取Map的第一个(最小的)节点对象
    27     Map.Entry<K,V> firstEntry();
    28 
    29     // 获取Map的最后一个(最大的)节点对象
    30     Map.Entry<K,V> lastEntry();
    31 
    32     // 获取Map的第一个节点对象,并从Map中移除改节点
    33     Map.Entry<K,V> pollFirstEntry();
    34 
    35     // 获取Map的最后一个节点对象,并从Map中移除改节点
    36     Map.Entry<K,V> pollLastEntry();
    37 
    38     // 返回当前Map的逆序Map集合
    39     NavigableMap<K,V> descendingMap();
    40 
    41     // 返回当前Map中包含的所有key的Set集合
    42     NavigableSet<K> navigableKeySet();
    43 
    44     // 返回当前map的逆序Set集合,Set由key组成
    45     NavigableSet<K> descendingKeySet();
    46 
    47     // 返回当前map中介于fromKey(fromInclusive是否包含)和toKey(toInclusive是否包含) 之间的子map
    48     NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
    49                              K toKey,   boolean toInclusive);
    50 
    51     // 返回介于map第一个元素到toKey(inInclusive是否包含)之间的子map
    52     NavigableMap<K,V> headMap(K toKey, boolean inclusive);
    53 
    54     // 返回当前map中介于fromKey(inInclusive是否包含) 到map最后一个元素之间的子map
    55     NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);
    56 
    57     // 返回当前map中介于fromKey(包含)和toKey(不包含)之间的子map
    58     SortedMap<K,V> subMap(K fromKey, K toKey);
    59 
    60     // 返回介于map第一个元素到toKey(不包含)之间的子map
    61     SortedMap<K,V> headMap(K toKey);
    62 
    63     // 返回当前map中介于fromKey(包含) 到map最后一个元素之间的子map
    64     SortedMap<K,V> tailMap(K fromKey);
    65 }
      从NavigableMap接口的方法中可以看出,基本上定义的都是一些边界的搜索和查询。当然这些方法是不能实现Set的,再看下NavigableMap的定义,NavigableMap继承了SortedMap接口,而SortedMap继承了Map接口,所以NavigableMap是在Map接口的基础上丰富了这些对于边界查询的方法,但是不妨碍你只是用其中Map中自身的功能。’
     
         下面先来看下TreeSet的基础功能吧:
     
    3.TreeSet的增加和删除
     1     /**
     2      * 利用NavigableMap的put方法实现add方法
     3      */
     4    public boolean add(E e) {
     5         return m .put(e, PRESENT)== null;
     6     }
     7      
     8     /**
     9      * 利用NavigableMap的remove方法实现add方法
    10      */
    11     public boolean remove(Object o) {
    12         return m .remove(o)==PRESENT;
    13     }
    14   
    15     /**
    16      * 添加一个集合到TreeSet中
    17      */
    18    public boolean addAll(Collection<? extends E> c) {
    19         // Use linear-time version if applicable
    20         // 如果集合c是SortedSet的子类,并且m是TreeMap的子类,则用下面的方法添加(主要为了检查是否需要重新排序)
    21         if (m .size()==0 && c.size() > 0 &&
    22            c instanceof SortedSet &&
    23             m instanceof TreeMap) {
    24             SortedSet<? extends E> set = (SortedSet<? extends E>) c;
    25             TreeMap<E,Object> map = (TreeMap<E, Object>) m;
    26             // 取出集合c的比较器
    27             Comparator<? super E> cc = (Comparator<? super E>) set.comparator();
    28             // 取出当前set的比较器
    29             Comparator<? super E> mc = map.comparator();
    30             // 如果上面的两种比较器是同一个的话(==或equals),当然TreeSet和TreeMap默认构造方法比较器都是null,这里也是==的
    31             if (cc==mc || (cc != null && cc.equals(mc))) {
    32                 // 将集合c在当前set集合顺序的基础上,按顺序插入
    33                 map.addAllForTreeSet(set, PRESENT);
    34                 return true;
    35             }
    36         }
    37 
    38         // 不需要排序的话就按普通方法,调用父类AbstractCollection的addAll方法(将集合c添加到Set尾部)
    39         return super.addAll(c);
    40     }
    41 
    42      
    43     /**
    44      * 添加一个集合到TreeSet中
    45      */
    46     public boolean removeAll(Collection<?> c) {
    47         boolean modified = false;
    48 
    49         // 判断当前TreeSet元素个数和指定集合c的元素个数,目的是减少遍历次数
    50         if (size() > c.size()) {
    51             // 如果当前TreeSet元素多,则遍历集合c,将集合c中的元素一个个删除
    52             for (Iterator<?> i = c.iterator(); i.hasNext(); )
    53                 modified |= remove(i.next());
    54         } else {
    55             // 如果集合c元素多,则遍历当前TreeSet,将集合c中包含的元素一个个删除
    56             for (Iterator<?> i = iterator(); i.hasNext(); ) {
    57                 if (c.contains(i.next())) {
    58                     i.remove();
    59                     modified = true;
    60                 }
    61             }
    62         }
    63         return modified;
    64     }

    4.是否包含

     1     /**
     2      * 利用TreeMap的containsKey方法实现contains方法
     3      */
     4    public boolean contains(Object o) {
     5         return m .containsKey(o);
     6     }
     7    
     8     /**
     9      * 检查是否包含指定集合中所有元素,该方法在AbstractCollection中
    10      */
    11     public boolean containsAll(Collection<?> c) {
    12        // 取得集合c的迭代器Iterator
    13        Iterator<?> e = c.iterator();
    14        // 遍历迭代器,只要集合c中有一个元素不属于当前HashSet,则返回false
    15         while (e.hasNext())
    16            if (!contains(e.next()))
    17                return false;
    18         return true;
    19     }

    5.容量检查

     1     /**
     2      * Returns the number of elements in this set (its cardinality).
     3      *
     4      * @return the number of elements in this set (its cardinality)
     5      */
     6     public int size() {
     7         return map .size();
     8     }
     9 
    10     /**
    11      * Returns <tt>true</tt> if this set contains no elements.
    12      *
    13      * @return <tt> true</tt> if this set contains no elements
    14      */
    15     public boolean isEmpty() {
    16         return map .isEmpty();
    17     }
      可以看到由于TreeSet底层基于TreeMap(默认情况下)实现,在代码层面上来看是非常简单的,但是如果想要透彻的明白TreeSet底层存储及其操作,还是要了解TreeMap底层红黑树的原理。
     
         到这里TreeSet的基本方法就分析完了,下面我们来看下,TreeSet实现于NavigableSet的一些边界搜索方法是怎么实现的。
     
    6.NavigableSet&NavigableMap
     
         如果没想错的话,TreeSet实现于NavigableSet的一些边界搜索方法也是基于NavigableMap实现的,我们随便拿两个方法实现来看一下:
    1 public E pollFirst() {
    2         Map.Entry<E,?> e = m.pollFirstEntry();
    3         return (e == null)? null : e.getKey();
    4     }
    5 
    6     public E pollLast() {
    7         Map.Entry<E,?> e = m.pollLastEntry();
    8         return (e == null)? null : e.getKey();
    9     }

      果然没有猜错,这些方法还是基于NavigableMap实现的,要明白其具体实现代码,我们来看看TreeMap中是怎么实现NavigableMap接口中这些方法的。

     1 public Map.Entry<K,V> pollFirstEntry() {
     2         // 取得当前Map第一个节点
     3         Entry<K,V> p = getFirstEntry();
     4         // 返回一个只包含key、value的简单Entry对象,exportEntry不必深究也很简单
     5         Map.Entry<K,V> result = exportEntry(p);
     6         // 如果节点不为空,将节点删除
     7         if (p != null)
     8             deleteEntry(p);
     9         return result;
    10     }
    11 
    12     public Map.Entry<K,V> pollLastEntry() {
    13         // 取得当前Map第一个节点
    14         Entry<K,V> p = getLastEntry();
    15         // 返回一个只包含key、value的简单Entry对象,exportEntry不必深究也很简单
    16         Map.Entry<K,V> result = exportEntry(p);
    17         // 如果节点不为空,将节点删除
    18         if (p != null)
    19             deleteEntry(p);
    20         return result;
    21     }
    22  
    23     /**
    24      * Returns the first Entry in the TreeMap (according to the TreeMap's
    25      * key -sort function).  Returns null if the TreeMap is empty.
    26      */
    27     final Entry<K,V> getFirstEntry() {
    28         // 取得根节点
    29         Entry<K,V> p = root;
    30         if (p != null)
    31             // 循环取根节点的left,直到取到最左边的一个节点,也就是取得最小值(红黑树原则最左边最小)
    32             while (p.left != null)
    33                 p = p. left;
    34         return p;
    35     }
    36 
    37     /**
    38      * Returns the last Entry in the TreeMap (according to the TreeMap's
    39      * key -sort function).  Returns null if the TreeMap is empty.
    40      */
    41     final Entry<K,V> getLastEntry() {
    42         // 取得根节点
    43         Entry<K,V> p = root;
    44         if (p != null)
    45             // 循环取根节点的right,直到取到最右边的一个节点,也就是取得最大值(红黑树原则最右边最大)
    46             while (p.right != null)
    47                 p = p. right;
    48         return p;
    49     }

      

      在明白了红黑树的原则之后,这几个取第一个和最后一个的方法看起来还是很简单的,我们再来看下其他方法的实现:

     1 public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,
     2                                         K toKey,   boolean toInclusive) {
     3             // key越界检查,key怎么越界呢,当然是因为TreMap已经对key排序了,不细看
     4             if (!inRange(fromKey, fromInclusive))
     5                 throw new IllegalArgumentException( "fromKey out of range" );
     6             if (!inRange(toKey, toInclusive))
     7                 throw new IllegalArgumentException( "toKey out of range" );
     8             // 返回AscendingSubMap对象
     9             return new AscendingSubMap(m,
    10                                        false, fromKey, fromInclusive,
    11                                        false, toKey,   toInclusive);
    12         }

      AscendingSubMap是NavigableSubMap子类,该构造方法直接调用NavigableSubMap,继续看:

     1 static abstract class NavigableSubMap<K,V> extends AbstractMap<K,V>
     2         implements NavigableMap<K,V>, java.io.Serializable {
     3         /**
     4          * The backing map.
     5          */
     6         final TreeMap<K,V> m; // 底层使用原始TreeMap提供数据操作
     7 
     8         final K lo, hi;
     9         final boolean fromStart, toEnd;
    10         final boolean loInclusive, hiInclusive;
    11 
    12         
    13      NavigableSubMap(TreeMap<K,V> m,
    14                         boolean fromStart, K lo, boolean loInclusive,
    15                         boolean toEnd,     K hi, boolean hiInclusive) {
    16             if (!fromStart && !toEnd) {
    17                 if (m.compare(lo, hi) > 0)
    18                     throw new IllegalArgumentException( "fromKey > toKey" );
    19             } else {
    20                 if (!fromStart) // type check
    21                     m.compare(lo, lo);
    22                 if (!toEnd)
    23                     m.compare(hi, hi);
    24             }
    25 
    26             // 记录边界
    27             this.m = m;
    28             this.fromStart = fromStart;
    29             this.lo = lo;
    30             this.loInclusive = loInclusive;
    31             this.toEnd = toEnd;
    32             this.hi = hi;
    33             this.hiInclusive = hiInclusive;
    34         }
    35                ... ...
    36                ... ...
    37 
    38      public final V put(K key, V value) {
    39             // 边界检查,如果不在边界范围内,则抛出异常
    40             if (!inRange(key))
    41                 throw new IllegalArgumentException( "key out of range" );
    42             return m .put(key, value);
    43         }
    44      public final V get(Object key) {
    45             return !inRange(key)? null :  m.get(key);
    46         }
    47      }
      上面的代码比较乱,这里总结一下,subMap这个方法要求返回一个介于fromKey、toKey范围内的字Map。在TreeMap的实现中,是靠一个内部Map的子类NavigableSubMap ,这个类将记录fromKey、toKey等,将这个子Map返回后,在操作这个子Map的put、get等操作的时候,都会检查是否在之前的限定内,如果是在限定内则抛出异常,也就是说实际上并不是对原Map的切割负责,底层继续使用原Map,只是给原Map加一个限定条件。
         想一想这样做的好处,如果是新创建一个子Map来存限定内的元素,或者复制原Map切割掉限定外的元素,这样的新创建都会在堆内存中申请一份内存空间;而TreeMap这样做,只是在一个类中加了一个指针指向原先的Map,这个指针只分配在栈空间,占用很小的一块内存,这样是不是节省内存空间了呢,虽然其他操作要先检查边界效率会低一些。其实这在设计模式上就叫做代理,实际上NavigableSubMap是TreeMap的一个静态代理类。但是这样存在的一个问题是什么呢,原Map和NavigableSubMap指向的是一块内存,当对NavigableSubMap进行添加、删除等修改操作的时候,实际上原Map也已经变化了。
         不知道上面的解释是否看明白,不明白的话去看看这个《jvm内存模型及分配参数》。。。
     
         NavigableMap的其他方法就不去逐一分析,很多都是subMap这个方法的重载方法,或者基于红黑树的查询方法,不明白的话要返回去将TreeMap的分析和红黑树的原理多多看几遍了。
     
     
         TreeSet&NavigableMap&NavigableSet 完!
    参见:
     
     
  • 相关阅读:
    zoj 3620 Escape Time II dfs
    zoj 3621 Factorial Problem in Base K 数论 s!后的0个数
    bzoj 1789: [Ahoi2008]Necklace Y型项链 贪心
    BZOJ 4027: [HEOI2015]兔子与樱花 树上dp
    Codeforces Beta Round #80 (Div. 1 Only) D. Time to Raid Cowavans 分块
    图论:单源最短路与多源最短路问题
    图论:图的概念与图的储存方式
    hdu 4802 GPA 水题
    Codeforces Round #202 (Div. 1) A. Mafia 贪心
    Spring Bean配置默认为单实例 pring Bean生命周期
  • 原文地址:https://www.cnblogs.com/tstd/p/5084385.html
Copyright © 2011-2022 走看看