TreeSet&NavigableMap&NavigableSet源碼解析 給jdk寫註釋系列之jdk1.6容器(8)

  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 完!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章