TreeMap使用紅黑樹儲存元素,可以保證元素按key值的大小進行遍歷。
繼承體系

TreeMap實現了Map、SortedMap、NavigableMap、Cloneable、Serializable等介面。
SortedMap規定了元素可以按key的大小來遍歷,它定義了一些傳回部分map的方法。
public interface SortedMap extends Map {// key的比較器Comparator super K> comparator();// 傳回fromKey(包含)到toKey(不包含)之間的元素組成的子mapSortedMap subMap(K fromKey, K toKey);// 傳回小於toKey(不包含)的子mapSortedMap headMap(K toKey);// 傳回大於等於fromKey(包含)的子mapSortedMap tailMap(K fromKey);// 傳回最小的keyK firstKey();// 傳回最大的keyK lastKey();// 傳回key集合Set keySet();// 傳回value集合Collection values();// 傳回節點集合Set<Map.Entry> entrySet();}
NavigableMap是對SortedMap的增強,定義了一些傳回離標的key最近的元素的方法。
public interface NavigableMap extends SortedMap {// 小於給定key的最大節點Map.Entry lowerEntry(K key);// 小於給定key的最大keyK lowerKey(K key);// 小於等於給定key的最大節點Map.Entry floorEntry(K key);// 小於等於給定key的最大keyK floorKey(K key);// 大於等於給定key的最小節點Map.Entry ceilingEntry(K key);// 大於等於給定key的最小keyK ceilingKey(K key);// 大於給定key的最小節點Map.Entry higherEntry(K key);// 大於給定key的最小keyK higherKey(K key);// 最小的節點Map.Entry firstEntry();// 最大的節點Map.Entry lastEntry();// 彈出最小的節點Map.Entry pollFirstEntry();// 彈出最大的節點Map.Entry pollLastEntry();// 傳回倒序的mapNavigableMap descendingMap();// 傳回有序的key集合NavigableSet navigableKeySet();// 傳回倒序的key集合NavigableSet descendingKeySet();// 傳回從fromKey到toKey的子map,是否包含起止元素可以自己決定NavigableMap subMap(K fromKey, boolean fromInclusive,K toKey, boolean toInclusive);// 傳回小於toKey的子map,是否包含toKey自己決定NavigableMap headMap(K toKey, boolean inclusive);// 傳回大於fromKey的子map,是否包含fromKey自己決定NavigableMap tailMap(K fromKey, boolean inclusive);// 等價於subMap(fromKey, true, toKey, false)SortedMap subMap(K fromKey, K toKey);// 等價於headMap(toKey, false)SortedMap headMap(K toKey);// 等價於tailMap(fromKey, true)SortedMap tailMap(K fromKey);}
儲存結構

TreeMap只使用到了紅黑樹,所以它的時間複雜度為O(log n),我們再來回顧一下紅黑樹的特性。
(1)每個節點或者是黑色,或者是紅色。
(2)根節點是黑色。
(3)每個葉子節點(NIL)是黑色。(註意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!)
(4)如果一個節點是紅色的,則它的子節點必須是黑色的。
(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
原始碼解析
屬性
/*** 比較器,如果沒傳則key要實現Comparable介面*/private final Comparator super K> comparator;/*** 根節點*/private transient Entry root;/*** 元素個數*/private transient int size = 0;/*** 修改次數*/private transient int modCount = 0;
(1)comparator
按key的大小排序有兩種方式,一種是key實現Comparable介面,一種方式透過構造方法傳入比較器。
(2)root
根節點,TreeMap沒有桶的概念,所有的元素都儲存在一顆樹中。
Entry內部類
儲存節點,典型的紅黑樹結構。
static final class Entry implements Map.Entry {K key;V value;Entry left;Entry right;Entry parent;boolean color = BLACK;}
構造方法
/*** 預設構造方法,key必須實現Comparable介面*/public TreeMap() {comparator = null;}/*** 使用傳入的comparator比較兩個key的大小*/public TreeMap(Comparator super K> comparator) {this.comparator = comparator;}/*** key必須實現Comparable介面,把傳入map中的所有元素儲存到新的TreeMap中*/public TreeMap(Map extends K, ? extends V> m) {comparator = null;putAll(m);}/*** 使用傳入map的比較器,並把傳入map中的所有元素儲存到新的TreeMap中*/public TreeMap(SortedMapextends V> m) {comparator = m.comparator();try {buildFromSorted(m.size(), m.entrySet().iterator(), null, null);} catch (java.io.IOException cannotHappen) {} catch (ClassNotFoundException cannotHappen) {}}
構造方法主要分成兩類,一類是使用comparator比較器,一類是key必須實現Comparable介面。
其實,筆者認為這兩種比較方式可以合併成一種,當沒有傳comparator的時候,可以用以下方式來給comparator賦值,這樣後續所有的比較操作都可以使用一樣的邏輯處理了,而不用每次都檢查comparator為空的時候又用Comparable來實現一遍邏輯。
// 如果comparator為空,則key必須實現Comparable介面,所以這裡肯定可以強轉// 這樣在構造方法中統一替換掉,後續的邏輯就都一致了comparator = (k1, k2) -> ((Comparable super K>)k1).compareTo(k2);
get(Object key)方法
獲取元素,典型的二叉查詢樹的查詢方法。
public V get(Object key) {// 根據key查詢元素Entry p = getEntry(key);// 找到了傳回value值,沒找到傳回nullreturn (p==null ? null : p.value);}final Entry getEntry(Object key) {// 如果comparator不為空,使用comparator的版本獲取元素if (comparator != null)return getEntryUsingComparator(key);// 如果key為空傳回空指標異常if (key == null)throw new NullPointerException();// 將key強轉為Comparable@SuppressWarnings("unchecked")Comparable super K> k = (Comparable super K>) key;// 從根元素開始遍歷Entry p = root;while (p != null) {int cmp = k.compareTo(p.key);if (cmp < 0)// 如果小於0從左子樹查詢p = p.left;else if (cmp > 0)// 如果大於0從右子樹查詢p = p.right;else// 如果相等說明找到了直接傳回return p;}// 沒找到傳回nullreturn null;}final Entry getEntryUsingComparator(Object key) {@SuppressWarnings("unchecked")K k = (K) key;Comparator super K> cpr = comparator;if (cpr != null) {// 從根元素開始遍歷Entry p = root;while (p != null) {int cmp = cpr.compare(k, p.key);if (cmp < 0)// 如果小於0從左子樹查詢p = p.left;else if (cmp > 0)// 如果大於0從右子樹查詢p = p.right;else// 如果相等說明找到了直接傳回return p;}}// 沒找到傳回nullreturn null;}
(1)從root遍歷整個樹;
(2)如果待查詢的key比當前遍歷的key小,則在其左子樹中查詢;
(3)如果待查詢的key比當前遍歷的key大,則在其右子樹中查詢;
(4)如果待查詢的key與當前遍歷的key相等,則找到了該元素,直接傳回;
(5)從這裡可以看出是否有comparator分化成了兩個方法,但是內部邏輯一模一樣,因此可見筆者 comparator=(k1,k2)->((Comparable
我是一條美麗的分割線,前方高能,請做好準備。
特性再回顧
(1)每個節點或者是黑色,或者是紅色。
(2)根節點是黑色。
(3)每個葉子節點(NIL)是黑色。(註意:這裡葉子節點,是指為空(NIL或NULL)的葉子節點!)
(4)如果一個節點是紅色的,則它的子節點必須是黑色的。
(5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
左旋
左旋,就是以某個節點為支點向左旋轉。

整個左旋過程如下:
(1)將 y的左節點 設為 x的右節點,即將 β 設為 x的右節點;
(2)將 x 設為 y的左節點的父節點,即將 β的父節點 設為 x;
(3)將 x的父節點 設為 y的父節點;
(4)如果 x的父節點 為空節點,則將y設定為根節點;如果x是它父節點的左(右)節點,則將y設定為x父節點的左(右)節點;
(5)將 x 設為 y的左節點;
(6)將 x的父節點 設為 y;
讓我們來看看TreeMap中的實現:
/*** 以p為支點進行左旋* 假設p為圖中的x*/private void rotateLeft(Entry p) {if (p != null) {// p的右節點,即yEntry r = p.right;// (1)將 y的左節點 設為 x的右節點p.right = r.left;// (2)將 x 設為 y的左節點的父節點(如果y的左節點存在的話)if (r.left != null)r.left.parent = p;// (3)將 x的父節點 設為 y的父節點r.parent = p.parent;// (4)...if (p.parent == null)// 如果 x的父節點 為空,則將y設定為根節點root = r;else if (p.parent.left == p)// 如果x是它父節點的左節點,則將y設定為x父節點的左節點p.parent.left = r;else// 如果x是它父節點的右節點,則將y設定為x父節點的右節點p.parent.right = r;// (5)將 x 設為 y的左節點r.left = p;// (6)將 x的父節點 設為 yp.parent = r;}}
右旋
右旋,就是以某個節點為支點向右旋轉。
整個右旋過程如下:
(1)將 x的右節點 設為 y的左節點,即 將 β 設為 y的左節點;
(2)將 y 設為 x的右節點的父節點,即 將 β的父節點 設為 y;
(3)將 y的父節點 設為 x的父節點;
(4)如果 y的父節點 是 空節點,則將x設為根節點;如果y是它父節點的左(右)節點,則將x設為y的父節點的左(右)節點;
(5)將 y 設為 x的右節點;
(6)將 y的父節點 設為 x;
讓我們來看看TreeMap中的實現:
/*** 以p為支點進行右旋* 假設p為圖中的y*/private void rotateRight(Entry p) {if (p != null) {// p的左節點,即xEntry l = p.left;// (1)將 x的右節點 設為 y的左節點p.left = l.right;// (2)將 y 設為 x的右節點的父節點(如果x有右節點的話)if (l.right != null) l.right.parent = p;// (3)將 y的父節點 設為 x的父節點l.parent = p.parent;// (4)...if (p.parent == null)// 如果 y的父節點 是 空節點,則將x設為根節點root = l;else if (p.parent.right == p)// 如果y是它父節點的右節點,則將x設為y的父節點的右節點p.parent.right = l;else// 如果y是它父節點的左節點,則將x設為y的父節點的左節點p.parent.left = l;// (5)將 y 設為 x的右節點l.right = p;// (6)將 y的父節點 設為 xp.parent = l;}}
未完待續,下一節我們一起探討紅黑樹插入元素的操作。
知識星球
朋友會在“發現-看一看”看到你“在看”的內容