TreeSet、TreeMap、Collections.sort()的區別,原理

1 使用上看

  • TreeSet要求,每一個元素要實現Comparable接口。
  • TreeMap要求鍵實現Comparable接口。
  • Collections.sort()有兩種重載方式:
    (1)元素實現Comparable接口。
    (2)向sort()方法中傳入一個Comparator實現類。
  1. TreeSet
    TreeSet要求存放的對象所屬的類必須實現Comparable接口,該接口提供了比較元素的compareTo()方法,當插入元素時會回調該方法比較元素的大小。

  2. TreeMap
    TreeMap要求存放的鍵值對映射的鍵必須實現Comparable接口,從而根據鍵對元素進行排序。

  3. Collections.sort()
    工具類的sort方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象必須實現Comparable接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,但是要求傳入第二個參數,參數是Comparator接口的子類型(需要重寫compare方法實現元素的比較),相當於一個臨時定義的排序規則,其實就是通過接口注入比較元素大小的算法,也是對回調模式的應用(Java中對函數式編程的支持)。

import sun.reflect.generics.tree.Tree;

import java.util.*;

public class JavaTest {
    public static void main(String[] args) {
        Student student1 = new Student("zhang2",10);
        Student student2 = new Student("zhang1",20);
        Student student3 = new Student("zhang3",30);

        /*TreeSet要求,每一個元素要實現Comparable接口。*/
        Set<Student> treeSet = new TreeSet<Student>();
        treeSet.add(student1);
        treeSet.add(student2);
        treeSet.add(student3);

        for (Object o : treeSet) {
            Student stu = (Student) o;
            System.out.println(stu.getName() + " : " + stu.getAge());
        }
        System.out.println();

        /*TreeMap要求鍵實現Comparable接口*/
        Map map = new TreeMap();
        map.put(student1,2);
        map.put(student2,3);
        map.put(student3,1);

        Set set = map.keySet();
        for (Object o : set) {
            Student stu = (Student) o;
            System.out.println(map.get(stu));
        }

        List<Student> list = new ArrayList<Student>();
        list.add(student1);
        list.add(student2);
        list.add(student3);

        /**
         * Collections.sort()有兩種重載方式:
         *  (1)元素實現Comparable接口。
         *  (2)向sort()方法中傳入一個Comparator實現類。
         */
        //Student實現了Comparable接口
        Collections.sort(list);
        for (Student student : list) {
            System.out.println(student.getName() + " : " + student.getAge());
        }

        List<Student> list2 = new ArrayList<Student>();
        list2.add(student1);
        list2.add(student2);
        list2.add(student3);

        Collections.sort(list2, new Comparator<Student>() {
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        for (Student student : list2) {
            System.out.println(student.getName() + " : " + student.getAge());
        }
    }
}

class Student implements Comparable<Student>{
    private String name;
    private Integer age;

    public Student(String name,Integer age){
        this.name = name;
        this.age = age;
    }

    public int compareTo(Student stu) {
        return this.name.compareTo(stu.getName());
    }

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }
}

2 原理

2.1 TreeSet與TreeMap

參考文獻:treeSet的底層實現
TreeMap 的實現就是紅黑樹數據結構,也就說是一棵自平衡的排序二叉樹,這樣就可以保證當需要快速檢索指定節點。

TreeSet 和 TreeMap 的關係

爲了讓大家瞭解 TreeMap 和 TreeSet 之間的關係,下面先看 TreeSet 類的部分源代碼:

 public class TreeSet<E> extends AbstractSet<E> 
    implements NavigableSet<E>, Cloneable, java.io.Serializable 
 { 
    // 使用 NavigableMap 的 key 來保存 Set 集合的元素
    private transient NavigableMap<E,Object> m; 
    // 使用一個 PRESENT 作爲 Map 集合的所有 value。
    private static final Object PRESENT = new Object(); 
    // 包訪問權限的構造器,以指定的 NavigableMap 對象創建 Set 集合
    TreeSet(NavigableMap<E,Object> m) 
    { 
        this.m = m; 
    } 
    public TreeSet()                                      // ①
    { 
        // 以自然排序方式創建一個新的 TreeMap,
        // 根據該 TreeSet 創建一個 TreeSet,
        // 使用該 TreeMap 的 key 來保存 Set 集合的元素
        this(new TreeMap<E,Object>()); 
    } 
    public TreeSet(Comparator<? super E> comparator)     // ②
    { 
        // 以定製排序方式創建一個新的 TreeMap,
        // 根據該 TreeSet 創建一個 TreeSet,
        // 使用該 TreeMap 的 key 來保存 Set 集合的元素
        this(new TreeMap<E,Object>(comparator)); 
    } 
    public TreeSet(Collection<? extends E> c) 
    { 
        // 調用①號構造器創建一個 TreeSet,底層以 TreeMap 保存集合元素
        this(); 
        // 向 TreeSet 中添加 Collection 集合 c 裏的所有元素
        addAll(c); 
    } 
    public TreeSet(SortedSet<E> s) 
    { 
        // 調用②號構造器創建一個 TreeSet,底層以 TreeMap 保存集合元素
        this(s.comparator()); 
        // 向 TreeSet 中添加 SortedSet 集合 s 裏的所有元素
        addAll(s); 
    } 
    //TreeSet 的其他方法都只是直接調用 TreeMap 的方法來提供實現
    ... 
    public boolean addAll(Collection<? extends E> c) 
    { 
        if (m.size() == 0 && c.size() > 0 && 
            c instanceof SortedSet && 
            m instanceof TreeMap) 
        { 
            // 把 c 集合強制轉換爲 SortedSet 集合
            SortedSet<? extends E> set = (SortedSet<? extends E>) c; 
            // 把 m 集合強制轉換爲 TreeMap 集合
            TreeMap<E,Object> map = (TreeMap<E, Object>) m; 
            Comparator<? super E> cc = (Comparator<? super E>) set.comparator(); 
            Comparator<? super E> mc = map.comparator(); 
            // 如果 cc 和 mc 兩個 Comparator 相等
            if (cc == mc || (cc != null && cc.equals(mc))) 
            { 
                // 把 Collection 中所有元素添加成 TreeMap 集合的 key 
                map.addAllForTreeSet(set, PRESENT); 
                return true; 
            } 
        } 
        // 直接調用父類的 addAll() 方法來實現
        return super.addAll(c); 
    } 
    ... 
 } 

從上面代碼可以看出,TreeSet 的 ① 號、② 號構造器的都是新建一個 TreeMap 作爲實際存儲 Set 元素的容器,而另外 2 個構造器則分別依賴於 ① 號和 ② 號構造器,由此可見,TreeSet 底層實際使用的存儲容器就是 TreeMap。

與 HashSet 完全類似的是,TreeSet 裏絕大部分方法都是直接調用 TreeMap 的方法來實現的,這一點讀者可以自行參閱 TreeSet 的源代碼,此處就不再給出了。

對於 TreeMap 而言,它採用一種被稱爲“紅黑樹”的排序二叉樹來保存 Map 中每個 Entry —— 每個 Entry 都被當成“紅黑樹”的一個節點對待。

2.2 Collections.sort()

Collections.sort方法底層就是調用的array.sort方法。
底層是TimSort實現的,這是jdk1.7新增的,以前是歸併排序。TimSort算法就是找到**數據中已經排好序的塊,然後按規則合併這些塊。**每一個塊是一個分區,每一個分區也叫一個run。

Timsort核心的過程
TimSort算法爲了減少對升序部分的回饋和對降序部分的性能倒退,將輸入按其升序和降序特點進行了分區。每次合併會將兩個運行合併成一個運行。合併的結果保存到棧中。合併直到消耗掉所有的運行,這時將棧上剩餘的跑合併到只剩一個跑爲止。這時這個僅剩的跑便是排好序的結果。
綜上述過程,Timsort算法的過程包括
(0)如何數組長度小於某個值,直接用二分插入排序算法
(1)找到各個塊,併入棧
(2)按規則合併運行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章