來,進來的小夥伴們,咱們認識一下。html
我是俗世遊子,在外流浪多年的Java程序猿java
前面咱們已經介紹了HashMap,今天咱們來看看Map的另一個子類:TreeMapapi
首先在介紹TreeMap以前,咱們先了解一些前置知識,往下看安全
在瞭解排序方式以前,咱們先來聊一聊什麼是:有序,無序,排序數據結構
有序oracle
保證插入的順序和在容器中存儲的順序是一致的,典型表明:ide
無序源碼分析
插入的順序和在容器中存儲的順序不一致的,典型表明:性能
排序優化
基於某種規則在迭代的時候輸出符合規則的元素順序, 好比:
那麼咱們來看具體的排序方式
那麼,如今有一種需求,就是咱們按照必定順序將集合中的元素進行輸出,那麼咱們該怎麼作呢?基於這種方式,Java爲咱們提供了兩種實現方式:
實現該接口須要一個實體對象,而後重寫其compareTo()
,咱們來看例子:
// 定義一個Student對象 class Student implements Comparable<Student> { public int id; public String name; public int age; public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } /** * 對比方法 */ @Override public int compareTo(Student o) { // 按照年齡從小到大的排序方式 return age - o.age; } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } } // 小案例 ArrayList<Student> students = new ArrayList<Student>(6) {{ add(new Student(1, "張三", 20)); add(new Student(2, "里斯", 18)); add(new Student(3, "王五", 38)); add(new Student(4, "趙柳", 10)); add(new Student(5, "天氣", 77)); }}; // 排序前的輸出 System.out.println("排序前的輸出:"); System.out.println(students); System.out.println("================"); // 排序操做 Collections.sort(students); System.out.println("排序後的輸出:"); System.out.println(students); /** 排序前的輸出 [Student{id=1, name='張三', age=20}, Student{id=2, name='里斯', age=18}, Student{id=3, name='王五', age=38}, Student{id=4, name='趙柳', age=10}, Student{id=5, name='天氣', age=77}] ================ [Student{id=4, name='趙柳', age=10}, Student{id=2, name='里斯', age=18}, Student{id=1, name='張三', age=20}, Student{id=3, name='王五', age=38}, Student{id=5, name='天氣', age=77}] **/
能夠看到咱們已經實現了按照年齡從小到大的順序進行排序的
這裏須要注意一點,在compareTo()
中,是傳入的對象和當前對象進行對比:
這種方式是經過外部類的方式進行編寫,仍是上面的代碼,咱們改一些地方:
Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { // 按照ID降序排序 return (int) (o2.id - o1.id); } }); /** 排序前的輸出: [Student{id=1, name='張三', age=20}, Student{id=2, name='里斯', age=20}, Student{id=3, name='王五', age=38}, Student{id=4, name='趙柳', age=10}, Student{id=5, name='天氣', age=77}] ================ 排序後的輸出: [Student{id=5, name='天氣', age=77}, Student{id=4, name='趙柳', age=10}, Student{id=3, name='王五', age=38}, Student{id=2, name='里斯', age=20}, Student{id=1, name='張三', age=20}] **/
查看,已經實現了需求
在compare()
中返回值的對比和第一種方式是同樣的
你們按照實際的需求選擇合理的排序方式吧
樹是一種數據結構,它是由n(n>=1)個有限結點組成一個具備層次關係的集合。把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具備如下的特色:
摘抄自:百度百科:Tree
二叉樹是樹形結構中的一種重要類型,是咱們在數據結構中最經常使用的樹結構之一,每一個節點下最多隻有兩個子節點
顧名思義,二叉搜索樹是以二叉樹來組織的,對比二叉樹,擁有如下特性:
二叉搜索樹的插入過程
也成AVL樹,是基於二叉搜索樹的一種擴展,也就是說擁有二叉搜索樹的所有特性。二叉搜索樹存在缺點:
AVL樹針對這一狀況進行了改進:
基於平衡樹的一種演進,也存在旋轉操做保持二叉樹的平衡,同時在此基礎上添加變色操做,擁有以下特性:
這裏沒有講解的很詳細,簡單的說一下有個概念
下面咱們來看一下TreeMap:
以前我說的有些問題:咱們遇到一個類,它最重要的是類中的註釋,咱們先來看TreeMap的註釋是如何介紹TreeMap的:
SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
下面咱們來具體看看咱們如何使用TreeMap的
TreeMap<String, String> treeMap = new TreeMap<>(); new TreeMap<String, Long>(new Comparator<String>() { @Override public int compare(String o1, String o2) { return 0; } });
public TreeMap() { comparator = null; } public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
第二個構造方法傳入一個比較器,這個咱們在 排序方式 中就已經說到,也明白了返回的含義
可是這裏要注意一點:若是咱們沒有傳入 比較器,默認爲null,那麼咱們須要明白:
在瞭解該方法以前,咱們先來了解一個類:
static final class Entry<K,V> implements Map.Entry<K,V> { K key; V value; Entry<K,V> left; Entry<K,V> right; Entry<K,V> parent; boolean color = BLACK; Entry(K key, V value, Entry<K,V> parent) { this.key = key; this.value = value; this.parent = parent; } }
咱們已經知道,TreeMap底層是採用紅黑樹的結構來存儲數據,那麼對應到代碼中的實現就是上面的樣子。
下面咱們來看具體是如何添加元素的
public V put(K key, V value) { Entry<K,V> t = root; // 根節點 if (t == null) { compare(key, key); // type (and possibly null) check 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 (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; 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); } // 賦值操做 Entry<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; // 變色,旋轉 fixAfterInsertion(e); size++; modCount++; return null; }
總結一下,能夠分爲四步來進行操做:
判斷若是當前根節點爲null,那麼當前插入的第一個元素就爲根節點元素
前面3點都很簡單,無非就是經過do..while
循環經過排序器進行對比,這裏有一點,也就是在構造方法裏我提到的一點:
class A { public Long id; } TreeMap<A, String> map = new TreeMap<>(); map.put(new A(), "11"); map.put(new A(), "11"); System.out.println(map); // java.lang.ClassCastException: zopx.top.study.jav.maps.A cannot be cast to java.lang.Comparable
在採用默認構造方法的時候,這樣的方式出現錯誤:類型轉換異常,這也就是爲何TreeMap:Key必需要實現Comparable
的緣由
下來咱們重點看看節點變色和旋轉操做
private void fixAfterInsertion(Entry<K,V> x) { // 將當前節點標記爲紅色 x.color = RED; // 當插入元素後出現不平衡,則進行調整 while (x != null && x != root && x.parent.color == RED) { // 判斷是不是左邊節點 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { Entry<K,V> y = rightOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == rightOf(parentOf(x))) { x = parentOf(x); rotateLeft(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } else { // 不然就是右邊節點 Entry<K,V> y = leftOf(parentOf(parentOf(x))); if (colorOf(y) == RED) { setColor(parentOf(x), BLACK); setColor(y, BLACK); setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } else { if (x == leftOf(parentOf(x))) { x = parentOf(x); rotateRight(x); } setColor(parentOf(x), BLACK); setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } // 根節點永遠是黑色 root.color = BLACK; }
下面我經過畫圖來進行代碼分析吧,這樣更容易理解:
這是一個最簡單的例子,節點也很是少,你們能夠本身按照上面的方式過一下代碼,理解下代碼的邏輯
右旋操做
private void rotateRight(Entry<K,V> p) { if (p != null) { Entry<K,V> l = p.left; p.left = l.right; if (l.right != null) l.right.parent = p; l.parent = p.parent; if (p.parent == null) root = l; else if (p.parent.right == p) p.parent.right = l; else p.parent.left = l; l.right = p; p.parent = l; } }
一樣,在紅黑樹中還包含左旋的操做,你們能夠本身看下源代碼:
rotateLeft()
,和右旋很相似最好是可以邊分析源代碼,邊經過畫圖的方式加深理解
上面也就是TreeMap基於紅黑樹的實現方式,你們能夠結合上面介紹的紅黑樹的特性好好理解下
remove(Object key)
方法和put()
方法相差很少,你們能夠本身看看源碼
下面簡單來講一下get()
方法:
Entry<K,V> p = getEntry(key); final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); // 默認構造器 @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { // 不斷對比,若是==0,那麼就是當前須要的Entry int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; } // 自定義排序方式 final Entry<K,V> getEntryUsingComparator(Object key) { @SuppressWarnings("unchecked") K k = (K) key; Comparator<? super K> cpr = comparator; if (cpr != null) { Entry<K,V> p = root; while (p != null) { // 不斷對比,若是==0,那麼就是當前須要的Entry int cmp = cpr.compare(k, p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } } return null; }
該方法仍是比較簡單的,也就是在while()
循環中經過比較器進行對比
更多關於TreeMap使用方法推薦查看其文檔: