Java集合--TreeSet詳細解析


談到TreeSet的特點,估計大家腦海里想到的都是:有序,不可重復,紅黑樹,基於Treemap實現,自定義排序等特點。這篇博客幫助大家從源碼梳理下TreeSet的知識點。

1.構造函數

TreeSet提供了四種構造器

  • TreeSet()
  • TreeSet(Collection< ? extends E> c)
  • TreeSet(Comparator< ? super E> comparator)
  • TreeSet(SortedSet< E > s)

四種構造器在底層都調用了同一個方法。以無參構造函數為例。[1]處的this方法最終調用的是[2]的方法,其中四個構造器的傳參都被TreeMap封裝了一層。

   public TreeSet() {
        this(new TreeMap<E,Object>()); //[1]
    }
    TreeSet(NavigableMap<E,Object> m) {//[2]
        this.m = m;
    }

2.增

TreeSet在添加元素時,會把元素放入TreeMap中的key上來確保元素的唯一性,並讓其value指向一個空對象。TreeSet#add()方法會調用TreeMap#put()方法添加元素,添加元素時,從樹的根節點開始遍歷直到找到新增元素的parent節點,添加進去。通過TreeMap的源碼可以看出維護的是一個紅黑樹數據結構。

PS:由於TreeSet的實例化時都會調用TreeMap的無參構造函數,此時
TreeMap#comparator=null;

   private static final Object PRESENT = new Object();
   
   public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
    
    public  boolean addAll(Collection<? extends E> c) {
        // Use linear-time version if applicable
        if (m.size()==0 && c.size() > 0 &&
            c instanceof SortedSet &&   //是否是SortedSet類或其子類
            m instanceof TreeMap) {
            SortedSet<? extends E> set = (SortedSet<? extends E>) c;
            TreeMap<E,Object> map = (TreeMap<E, Object>) m;
            Comparator<?> cc = set.comparator();
            Comparator<? super E> mc = map.comparator();
            if (cc==mc || (cc != null && cc.equals(mc))) {//[3]
                map.addAllForTreeSet(set, PRESENT);
                return true;
            }
        }
        return super.addAll(c); // 不是SortedSet子類,就是Collection子類
    }

3.刪

TreeSet中提供了兩個和刪除相關的方法。
TreeSet#clear()復用了TreeMap#clear()方法,把root節點置為null,size置為0;
通過TreeSet#remove()移除特定元素時,TreeSet首先先遍歷出該元素,然后將紅黑樹中的元素置為null,重新平衡紅黑樹。

   public boolean remove(Object o) {
        return m.remove(o)==PRESENT;
    }
    
       public void clear() {
        m.clear();
    }
    
        /**
     * Delete node p, and then rebalance the tree.
     */
    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;
        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

4.比較器

TreeSet中有兩種排序,一個是自然排序,一個是重寫compareTo()方法自定義排序。
自然排序可以參考Integer,String等類中的實現。其順序也是我們常見的“1,2,3,4”,“a,b,c,d”。假如我們想讓Student對象中String類型的字段倒序輸出呢

@Data
public class Student implements Comparable<Student>{
    String name;
    /**
     * 這里的參數o,其實是TreeMap中維護的根節點
     * @param o
     * @return
     */
    @Override
    public int compareTo(Student o) {
        System.out.println("name:"+name+",參數:"+o.getName());
        int i = this.name.compareTo(o.getName());
       return i==0?0:-i;
    }
}

public static void main(String[] args) {
        Set<Student> set = new TreeSet<>();
        Student a = new Student();
        a.setName("a");
        Student b = new Student();
        b.setName("b");
        Student c = new Student();
        c.setName("c");
        Student d = new Student();
        d.setName("d");
        Student e = new Student();
        e.setName("e");
        Student f = new Student();
        f.setName("f");
        set.add(a);
        set.add(c);
        set.add(e);
        set.add(b);
        set.add(d);
        set.add(f);
        for (Student str: set) {
            System.out.print(str.getName());
        }
    }

其結果如下:

從打印的日志可以看出,每次插入新的元素,都會從根節點開始遍歷比較。當然TreeSet中也提供了我們倒序輸出的方法。有興趣可以自己試驗下。

  • descendingSet()
  • descendingIterator()

總結

TreeSet是通過TreeMap實現的一個有序的、不可重復的集合,底層維護的是紅黑樹結構。當TreeSet的泛型對象不是java的基本類型的包裝類時,對象需要重寫Comparable#compareTo()方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2024 CODEPRJ.COM