一、前言
當我們需要把插入的元素進行排序的時候,就是時候考慮TreeMap了,從名字上來看,TreeMap肯定是和樹是脫不了干系的,它是一個排序了的Map,下面我們來着重分析其源碼,理解其底層如何實現排序功能。下面,開始分析。
二、TreeMap示例

import java.util.TreeMap; import java.util.Map; public class TreeMapTest { public static void main(String[] args) { Map<String, String> maps = new TreeMap<String, String>(); maps.put("aa", "aa"); maps.put("cc", "cc"); maps.put("bb", "bb"); for (Map.Entry<String, String> entry : maps.entrySet()) { System.out.println(entry.getKey() + " : " + entry.getValue()); } } }
運行結果:
aa : aa
bb : bb
cc : cc
說明:從輸出結果可以看到TreeMap對插入的元素進行了排序。
三、TreeMap數據結構
TreeMap底層使用的數據結構是紅黑樹,有印象的的讀者,應該知道我們在分析HashMap的時候就已經接觸到了紅黑樹結構,只是沒有對紅黑樹進行詳細的分析,現在,筆者也並不打算對紅黑樹做太過仔細的分析,因為筆者之后會出數據結構的專題(先挖個坑),到時候再來一睹各種數據結構的風采。
說明:上圖為典型的紅黑樹結構,效率很高,具體的細節問題,我們以后詳談。
四、TreeMap源碼分析
4.1 類的繼承關系
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
說明:繼承了抽象類AbstractMap,AbstractMap實現了Map接口,實現了部分方法。不能進行實例化,實現了NavigableMap,Cloneable,Serializable接口,其中NavigableMap是繼承自SortedMap的接口,定義了一系列規范。
4.2 類的屬性

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable { // 比較器,用於控制Map中的元素順序 private final Comparator<? super K> comparator; // 根節點 private transient Entry<K,V> root; // 樹中結點個數 private transient int size = 0; // 對樹進行結構性修改的次數 private transient int modCount = 0; }
說明:重點是比較器Comparator,此接口實現了對插入元素進行排序。
4.3 類的構造函數
1. TreeMap()型構造函數

public TreeMap() { // 無用戶自定義比較器 comparator = null; }
2. TreeMap(Comparator<? super K>)型構造函數

// 自定義了比較器 public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; }
說明:用戶自定義了比較器,可以按照用戶的邏輯進行比較,確定元素的訪問順序。
3. TreeMap(Map<? extends K, ? extends V>)型構造函數

// 從已有map中構造TreeMap public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); }
說明:根據已有的Map構造TreeMap。
4. TreeMap(SortedMap<K, ? extends V>)型構造函數

// 從SortedMap中構造TreeMap,有比較器 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) { } }
說明:傳入SortedMap型參數,實現SortedMap接口的類都會實現comparator方法,用於返回比較器。
4.4 核心函數分析
1. put函數

public V put(K key, V value) { // 記錄根節點 Entry<K,V> t = root; // 根節點為空 if (t == null) { // 比較key compare(key, key); // type (and possibly null) check // 新生根節點 root = new Entry<>(key, value, null); // 大小加1 size = 1; // 修改次數加1 modCount++; return null; } int cmp; Entry<K,V> parent; // 獲取比較器 Comparator<? super K> cpr = comparator; // 比較器不為空 if (cpr != null) { // 找到元素合適的插入位置 do { // parent賦值 parent = t; // 比較key與元素的key值,在Comparator類的compare方法中可以實現我們自己的比較邏輯 cmp = cpr.compare(key, t.key); // 小於結點key值,向左子樹查找 if (cmp < 0) t = t.left; // 大於結點key值,向右子樹查找 else if (cmp > 0) t = t.right; // 表示相等,直接更新結點的值 else return t.setValue(value); } while (t != null); } // 比較器為空 else { // key為空,拋出異常 if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") // 取得K實現的比較器 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); // 大小加1 size++; // 進行了結構性修改 modCount++; return null; }
說明:插入一個元素時,若用戶自定義比較器,則會按照用戶自定義的邏輯確定元素的插入位置,否則,將會使用K自身實現的比較器確定插入位置。
2. getEntry函數

final Entry<K,V> getEntry(Object key) { // 判斷比較器是否為空 if (comparator != null) // 根據自定義的比較器來返回結果 return getEntryUsingComparator(key); // 比較器為空 // key為空,拋出異常 if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") // 取得K自身實現了比較接口 Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; // 根據Comparable接口的compareTo函數來查找元素 while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; }
說明:當我們調用get函數時,實際上是委托getEntry函數獲取元素,對於用戶自定義實現的Comparator比較器而言,是使用getEntryUsingComparator函數來完成獲取邏輯。
具體代碼如下

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) { int cmp = cpr.compare(k, p.key); // 小於結點key值,向左子樹查找 if (cmp < 0) p = p.left; // 大於結點key值,向右子樹查找 else if (cmp > 0) p = p.right; // 相等,找到,直接返回 else return p; } } return null; }
說明:會根據用戶定義在compare函數里面的邏輯進行元素的查找。
3. deleteEntry函數

private void deleteEntry(Entry<K,V> p) { // 結構性修改 modCount++; // 大小減1 size--; // p的左右子結點均不為空 if (p.left != null && p.right != null) { // 找到p結點的后繼 Entry<K,V> s = successor(p); // 將p的值用其后繼結點的key-value替換,並且用s指向其后繼 p.key = s.key; p.value = s.value; p = s; } // 開始進行修正,具體的修正過程我們會在之后的數據結構專區進行講解 // 現在可以看成是為了保持紅黑樹的特性,提高性能 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; } } }
說明:deleteEntry函數會在remove函數中被調用,它完成了移除元素的主要工作,刪除該結點后會對紅黑樹進行修正,此部分內容以后會詳細講解,同時,在此函數中需要調用successor函數,即找到該結點的后繼結點。具體函數代碼如下

// 找到后繼 static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) { // t為null,直接返回null if (t == null) return null; // 右孩子不為空 else if (t.right != null) { // 找到右孩子的最底層的左孩子,返回 Entry<K,V> p = t.right; while (p.left != null) p = p.left; return p; } else { // 右孩子為空 // 保存t的父節點 Entry<K,V> p = t.parent; // 保存t結點 Entry<K,V> ch = t; // 進行回溯,找到后繼,直到p == null || ch != p.right while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } }
說明:當結點的右子樹為空的時候,進行回溯可以找到該結點的后繼結點。
五、問題擴展
1. 如何找到小於指定結點的最大結點?參考getLowerEntry函數源碼
2. 如何找到大於指定結點的最小結點?參考getHigherEntry函數源碼
對getLowerEntry源碼分析如下

final Entry<K,V> getLowerEntry(K key) { // 保存根節點 Entry<K,V> p = root; // 根節點不為空 while (p != null) { // 比較該key與節點的key int cmp = compare(key, p.key); if (cmp > 0) { // 如果該key大於結點的key // 如果結點的右子樹不為空,與該結點右結點進行比較 if (p.right != null) p = p.right; else // 右子樹為空,則直接返回結點;因為此時已經沒有比該結點key更大的結點了(右子樹為空) return p; } else { // 如果該key小於等於結點的key // 結點的左子樹不為空,與該結點的左結點進行比較 if (p.left != null) { p = p.left; } else { // 結點的左子樹不為空,則開始進行回溯 Entry<K,V> parent = p.parent; Entry<K,V> ch = p; while (parent != null && ch == parent.left) { ch = parent; parent = parent.parent; } return parent; } } } return null; }
流程圖如下:
getHigherEntry則可以以此類推。
六、總結
由TreeMap我們可以知道其底層的數據結構為紅黑樹,並且可以使用用戶自定義的比較器來實現比較邏輯。對於其核心函數的分析就到此為止了,謝謝各位園友的觀看~