我原本打算仔細的去分析分析TreeSet和TreeMap排序規則,而且從底層實現和數據結構入手。當我去讀完底層源碼之後,我感受我就的目標定的太大了,單單就是數據結構就夠我本身寫好久了,所以我決定先易後難,先把底層源碼以及最直接的數據結構分析一下,至於底層的平衡二叉樹以及紅黑二叉樹,我就不過多去介紹,由於這是底層源碼優化用的,與直接實現代碼沒有多大關係,感興趣的也能夠去仔細研究。java
樹: 樹是n ( n >=0)個節點的有限集。n = 0時稱爲空樹。在任意一顆非空樹種中: (1)有且僅有一個特定的稱爲根(Root)的節點;(2)當n > 1時,其他節點可分爲m(m > 0)個互不相交的有限集T一、T二、T3......Tm,其中集合自己又是一顆樹,而且稱爲根的子樹。以下圖:數組
節點的子樹的根稱爲該節點的孩子,相應地,該節點稱爲孩子的雙親。爲何叫雙親,而不是父母呢?由於對於節點來講其父母同體,惟一的一個,因此只能把它稱爲雙親。同一個雙親的孩子之間稱爲兄弟。以下圖:數據結構
樹的其餘相關概念:app
層:節點的層次是從根開始定義的,根稱爲第一層,根的孩子稱爲第二層。樹中節點的最大層次稱爲樹的高度或深度。以下圖:優化
若是將樹中節點的各個子樹當作從左到右是有次序的,不能互換的,則稱該樹爲有序樹,不然稱爲無序樹。ui
二叉樹this
二叉樹:二叉樹是n (n >= 0)個節點的有序集合,該集合或者爲空集(稱爲空二叉樹),或者由一個根節點和兩顆互不相交的、分別稱爲根節點的左子樹和右子樹的二叉樹組成。spa
二叉樹的特色:
設計
一、每一個節點最多有兩顆子樹,因此二叉樹中不存在度大於2的節點。注意不是隻有兩顆子樹,而是最多有。沒有子樹或者有一顆子樹都是能夠的。3d
二、左子樹和右子樹是有順序的,次序不能任意顛倒。
三、即便樹種某節點只有一顆子樹,也要區分它是左子樹仍是右子樹。由於左子樹和右子樹是徹底不一樣的概念,區別特別重要。
二叉樹的形態:
一、空二叉樹
二、只有一個根節點
三、根節點只有左子樹
四、根節點只有右子樹
五、根節點既有左子樹,又有右子樹。對應下面5附圖:
二叉樹的存儲結構:
一、二叉樹的順序存儲結構
二、二叉樹的連式存儲結構(二叉鏈表)
順序存儲結構:順序存儲結構就是用一維數組存儲二叉樹中的節點,而且節點的存儲,也就是數組的下標要能體現節點之間的邏輯關係,好比雙親與孩子的關係,左右兄弟的關係等。
存儲前:
存儲後:
二叉鏈表:二叉樹每一個節點最多有2個孩子,因此爲它設計一個數據域和兩個指針域。結構圖以下:
二叉樹的遍歷:前序遍歷、中序遍歷、後序遍歷、層序遍歷。具體遍歷我就不累贅了。
二叉排序樹:
二叉排序樹:二叉排序樹,又稱爲二叉查找樹。它或者是一顆空樹,或者是具備下列性質的二叉樹。
一、若它的左子樹不空,則左子樹上全部節點的值均小於它的根結構的值。
二、若它的右子樹不空,則右子樹上全部節點的值均大於它的根節點的值。
三、它的左、右子樹也分別爲二叉排序樹。
典型案例就是數字遊戲:我在紙上寫好了一個100之內的正整數數字,你們來猜我寫的是哪個數字。注意,大家在纔對過程當中我只會回答「大了」 或 「 小了 」。
其實,這是一個很典型的折半查找法,就是對二叉排序樹的典型應用。以下圖:
源碼解讀:
首先,咱們看看TreeMap中須要用到的二叉樹的類結構:
static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value;
//記錄左子樹 Entry<K,V> left = null;
//記錄右子樹 Entry<K,V> right = null;
//記錄雙親節點 Entry<K,V> parent;
//紅黑二叉樹使用的根節點默認顏色 boolean color = BLACK; /** * Make a new cell with given key, value, and parent, and with * {@code null} child links, and BLACK color. */ Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; }
//省略不少具體的方法
TreeMap成員變量和構造方法:
//排序規則輔助類 private final Comparator<? super K> comparator; //記錄根節點
private transient Entry<K,V> root = null; /** * The number of entries in the tree */ private transient int size = 0; /** * The number of structural modifications to the tree. */ private transient int modCount = 0; public TreeMap() { comparator = null; } //本文重點分析的構造方法 public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } }
構造方法比較多,我本篇穩重重點說排序功能,所以我就選 public TreeMap(Comparator<? super K> comparator) 方法進行突破。而Comparator就是JDK自帶的排序輔助類,這個咱們後面講。
分析put方法:public V put(K key, V value) { Entry<K,V> t = root;
//若是根節點爲null將傳入的鍵值對構形成根節點 if (t == null) { compare(key, key); // type (and possibly null) check //根節點沒有父節點,因此傳入null root = new Entry<>(key, value, null); size = 1; modCount++; return null; }
//記錄比較結果 int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator;
//如下的if...else很是重要,主要是定位具體的節點(這個節點是做爲父節點的,咱們將新傳入的key/value插入到這個具體的節點下)
//有比較器的狀況
if (cpr != null) {
//dowhile實如今root爲根節點移動尋找傳入鍵值對須要插入的位置 do {
//記錄將要被插入新的鍵值對的節點 parent = t;
//比較器,按照自定義的規則返回結果 cmp = cpr.compare(key, t.key);
//插入的key較大 if (cmp < 0) t = t.left;
//插入的key較小 else if (cmp > 0) t = t.right;
//若是key相等,則直接替換value else return t.setValue(value); } while (t != null); }
//沒有傳入比較器 else { if (key == null) throw new NullPointerException(); Comparable<? super K> k = (Comparable<? super K>) key;
//與上方的do..while同樣,知識比較的規則不一樣 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); }
//沒有找到相同的key,纔會有此如下的方法操做。不然直接在上方就直接t.setValue(value)了
//根據key、value以及雙親節點,建立一個新的節點 Entry<K,V> e = new Entry<>(key, value, parent);
//若是最後一次判斷的結果,確認新節點是父節點的左孩子,仍是右孩子;爲何說是最後一次判斷的結果呢?由於上面的if...else...中都有while方法,而這個while就是爲了找這個最後的一次比較的結果 if (cmp < 0) parent.left = e; else parent.right = e;
//此方法我就不介紹了,涉及到紅黑二叉樹以及二叉樹的搖擺,對二叉樹進行優化操做 fixAfterInsertion(e); size++; modCount++; return null; }
至此,咱們發現,二叉樹的插入式根據cmp的值進行操做的,小於0就放在左子樹,大於0就放在右子樹。這不就是典型的二叉排序樹啊?還記得以前說的猜數字遊戲麼?
由此可知:
一、TreeMap底層的二叉樹是按照二叉排序樹的結構進行存儲的,左側小於根節點,右側大於根節點
二、至因而大於父節點,仍是小於父節點,那就是咱們本身定義的Comparator比較器的事情了。正常的狀況下,咱們知道1小於2;可是若是是自定義比較器,那麼咱們徹底能夠自定義1大於2;這種狀況下也就出現了所謂的升序和降序了。
說了這麼多,也許好多人還不是很明白。那麼接下來,我就舉幾個例子進行說明吧:
案例1:根據key的長度升序
public <T> void test1() { Map<String, String> map = new TreeMap<String, String>( new Comparator<String>() { public int compare(String o1, String o2) { return o1.length() - o2.length(); } }); map.put("hello", "我是hello"); map.put("jk", "咱們認識嗎?"); map.put("oooooo", "我要去香山看紅葉"); Set<Entry<String, String>> set = map.entrySet(); System.out.println("-----------------test1 : "); for (Iterator iter = set.iterator(); iter.hasNext();) { Entry<String, String> entry = (Entry<String, String>) iter.next(); System.out.println(entry.getKey() + " : " + entry.getValue()); } }
這個案例是升序,由於TreeMap調用compare(T o1, T o2)傳入的是能夠的值,所以,此處o1是新插入的key,而o2則是咱們源碼提到的do...while...中說道的找到的最後一次排序的key。而若是咱們想降序,只要將compare(T o1, T o2)實現方法中的 return o1.length() - o2.length();改爲 return o2.length() - o1.length();便可。容許結果以下圖:
-----------------test1 :
jk : 咱們認識嗎?
hello : 我是hello
oooooo : 我要去香山看紅葉
那麼假如咱們按照value進行排序,那又該怎麼辦呢?咱們看過底層的源碼實現,TreeMap沒有提供說put的時候,能夠進行對value的操做,所以要想直接經過TreeMap對value的值進行排序,那是不現實的。那若是咱們的業務非要對value進行排序又該怎麼辦呢?以下:
//根據value排序 public void sortByValue() { Map<String, String> map = new HashMap<String, String>(); map.put("a3", "dddd"); map.put("d", "aaaa"); map.put("b435", "cccc"); map.put("c6323", "bbbb"); List<Entry<String, String>> list = new ArrayList<Entry<String, String>>( map.entrySet()); Collections.sort(list, new Comparator<Map.Entry<String, String>>() { // 升序排序 public int compare(Entry<String, String> o1,Entry<String, String> o2) { return o1.getValue().compareTo(o2.getValue()); } }); System.out.println("sortByValue =" + list); }
看了這個實現,其實咱們並無對HashMap進行排序,而是在遍歷的時候對存放二叉樹Entry的list進行排序的,運行結果以下:
sortByValue =[d=aaaa, c6323=bbbb, b435=cccc, a3=dddd]
TreeSet源碼:
TreeSet的構造方法:
TreeSet(NavigableMap<E,Object> m) { this.m = m; } public TreeSet() { this(new TreeMap<E,Object>()); } public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); } public TreeSet(Collection<? extends E> c) { this(); addAll(c); } public TreeSet(SortedSet<E> s) { this(s.comparator()); addAll(s); }
接下來,我將會圍繞 public TreeSet(Comparator<? super E> comparator) 進行拓展:
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
而TreeMap上面已經分析過了,咱們知道TreeMap默認的是對key進行排序的,而TreeMap的構造方法竟然在構建一個TreeMap方法,接下來接續分析
add方法:
public boolean add(E e) { return m.put(e, PRESENT)==null; }
remove方法:
public boolean remove(Object o) { return m.remove(o)==PRESENT; }
first方法、last方法:
public E first() { return m.firstKey(); } /** * @throws NoSuchElementException {@inheritDoc} */ public E last() { return m.lastKey(); }
iterator方法:
public Iterator<E> iterator() { return m.navigableKeySet().iterator(); }
看完實現方法,所有是對m進行操做,而這個m是什麼呢?就是咱們以前的TreeMap。TreeMap已經分析過了,而TreeSet只是在調用TreeMap而已,所以廢話就很少說了。
總結:
一、TreeMap只能經過對key進行排序操做,沒法直接對value進行排序操做;而TreeSet的底層實現則是TreeMap,所以TreeSet也value也就是TreeMap的key,所以TreeSet是能夠對value進行各類排序的;
二、Comparator根本不能排序,它只是自定義的一種規則;而這個規則,TreeMap已經在底層對它進行封裝和調用了;
三、若是咱們想要對TreeMap的value進行操做的話,能夠藉助集合輔助類Collections進行操做