TreeMap 是一種基於紅黑樹實現的 Key-Value 結構。在使用集合視圖在 HashMap 中迭代時,是不能保證迭代順序的; LinkedHashMap 使用了雙向鏈表,保證按照插入順序或者訪問順序進行迭代。可是有些時候,咱們可能須要按照鍵的大小進行按序迭代,或者在使用哈希表的同時但願按鍵值進行排序,這個時候 TreeMap 就有其用武之地了。 TreeMap 支持按鍵值進行升序訪問,或者由傳入的比較器(Comparator)來控制。html
下面基於 JDK 8 的源碼對 TreeMap 進行一個簡單的分析。java
1 |
public class TreeMap<K,V> |
同 HashMap 同樣, TreeMap 也繼承了 AbstractMap,並實現了 Cloneable, Serializable 接口。不一樣的是, TreeMap 還實現 NavigableMap 接口。node
SortedMap 是一個擴展自 Map 的一個接口,對該接口的實現要保證全部的 Key 是徹底有序的。算法
這個順序通常是指 Key 的天然序(實現 Comparable 接口)或在建立 SortedMap 時指定一個比較器(Comparator)。當咱們使用集合的視角(Collection View,由 entrySet、keySet 與 values 方法提供)來迭代時,就能夠按序訪問其中的元素。api
插入 SortedMap 中的全部 Key 的類都必須實現 Comparable 接口(或者能夠做爲指定的 Comparator 的參數)。在比較兩個 Key 時經過調用 k1.compareTo(k2)
(or comparator.compare(k1, k2)
),於是全部的 Key 都必須可以相互比較,不然會拋出 ClassCastException
的異常。安全
SortedMap 中 Key 的順序必須和 equals 保持一致(consistent with equals),
即 k1.compareTo(k2) == 0
(or comparator.compare(k1, k2)
) 和 k1.equals(k2)
要有相同的布爾值。(Comparable 接口的實現不強制要求這一點,但一般都會遵照。)這是由於 Map 接口的定義中,比較 Key 是經過 equals 方法,而在 SortedMap 中比較 Key 則是經過 compareTo (or compare) 方法。若是不一致的,就破壞了 Map 接口的約定。多線程
經過 SortedMap 能夠獲取其中的一段數據,如 subMap(K fromKey, K toKey)
, headMap(K toKey)
, tailMap(K fromKey)
等,全部的區間操做都是左閉右開的。也能夠經過 firstKey()
和 lastKey()
來獲取第一個和最後一個鍵。併發
NavigableMap 是 JDK 1.6 以後新增的接口,擴展了 SortedMap 接口,提供了一些導航方法(navigation methods)來返回最接近搜索目標的匹配結果。oracle
lowerEntry(K key)
(or lowerKey(K key)
),小於給定 Key 的 Entry (or Key)floorEntry(K key)
(or floorKey(K key)
),小於等於給定 Key 的 Entry (or Key)higherEntry(K key)
(or higherKey(K key)
),大於給定 Key 的 Entry (or Key)ceilingEntry(K key)
(or ceilingKey(K key)
),大於等於給定 Key 的 Entry (or Key)這些方法都有重載的版本,來控制是否包含端點。subMap(K fromKey, K toKey)
, headMap(K toKey)
, tailMap(K fromKey)
等方法也是如此。this
NavigableMap 能夠按照 Key 的升序或降序進行訪問和遍歷。 descendingMap()
和 descendingKeySet()
則會獲取和原來的順序相反的集合,集合中的元素則是一樣的引用,在該視圖上的修改會影響到原始的數據。
TreeMap 是基於紅黑樹來實現的,排序時按照鍵的天然序(要求實現 Comparable 接口)或者提供一個 Comparator 用於排序。
1 |
//比較器,沒有指定的話默認使用Key的天然序 |
TreeMap 一樣不是線程安全的,基於結構化修改的次數來實現 fail-fast 機制。於是要在多線程環境下使用時,可能須要手動進行同步,或者使用 Collections.synchronizedSortedMap
進行包裝。
TreeMap 中的紅黑樹使用的是「算法導論」中的實現,除了左右連接、紅黑標識之外,還有一個指向父節點的鏈接。紅黑樹的具體插入及刪除細節這裏不做過多的解釋,更深刻的細節能夠參考「算法導論」一書,不過建議先看一下 Sedgewick 的講解。
1 |
//Entry (紅黑樹節點的定義) |
爲了維持有序,添加及更新的代價較高,複雜度爲 O(log(n)) 。插入節點後須要修復紅黑樹,使其恢復平衡狀態,該操做在此不做介紹。
1 |
public V put(K key, V value) { |
從紅黑樹中刪除一個節點比插入更爲複雜,這裏不做展開。
1 |
public V remove(Object key) { |
1 |
private void deleteEntry(Entry<K,V> p) { |
紅黑樹也是排序二叉樹,按照排序二叉樹的查找方法進行查找。複雜度爲 O(log(n)) 。
1 |
public V get(Object key) { |
判斷是否包含 key 或 value :
1 |
public boolean containsKey(Object key) { |
NaviableMap 接口支持一系列的導航方法,有 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry() 等,它們的實現原理都是相似的,區別在於如何在排序的二叉樹中查找到對應的節點。
以 lowerEntry() 和 floorEntry() 爲例:
1 |
//小於給定的Key |
查找的過程能夠和前驅節點的方法進行類比。 TreeMap 並無直接暴露 getLowerEntry() 方法,而是使用 exportEntry(getLowerEntry(key))
進行了一次包裝。看似「畫蛇添足」,其實是爲了防止對節點進行修改。SimpleImmutableEntry 類能夠看做不可修改的 Key-Value 對,由於成員變量 key 和 value 都是 final 的。
即經過暴露出來的接口 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry() 是不能夠修改獲取的節點的,不然會拋出異常。
1 |
/** |
pollFirstEntry() 、 pollLastEntry() 獲取第一個和最後一個節點,並將它們從紅黑樹中刪除。
1 |
public Map.Entry<K,V> pollFirstEntry() { |
能夠按照鍵的順序遍歷對 TreeSet 進行遍歷,由於底層使用了紅黑樹來保證有序性,迭代器的實現就是按序訪問排序二叉樹中的節點。
先看一些內部抽象類 PrivateEntryIterator
,它是 TreeMap 中全部迭代器的基礎:
1 |
abstract class PrivateEntryIterator<T> implements Iterator<T> { |
由於紅黑樹自身就是有序的,迭代是隻要從第一個節點不斷獲取後繼節點便可。固然,逆序時則是從最後一個節點不斷獲取前驅節點。經過迭代器訪問時基於 modCount 實現對併發修改的檢查。
在排序二叉樹中獲取前驅和後繼節點的方法以下:
1 |
//後繼節點 |
TreeMap 中還實現了一些其它的方法,如區間操做: headMap(), tailMap(), subMap() ; 獲取逆序的 map: descendingMap()
, descendingKeySet()
。只要瞭解了前面介紹的各類操做的原理,再來看這些方法的實現應該也不難理解。因爲篇幅太長,這裏就再也不介紹了。
TreeMap 是基於紅黑樹實現的一種 Key-Value 結構,最大的特色在於能夠按照 Key 的順序進行訪問,要求 Key 實現 Comparable 接口或傳入 Comparator 做爲比較器。由於基於紅黑樹實現,TreeMap 內部在實現插入和刪除操做時代價較高。
TreeMap 實現了 NavigableMap 接口,能夠支持一系列導航方法,有 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry() ;還能夠支持區間操做獲取 map 的一部分,如 subMap(), headMap(), tailMap(K fromKey) 。除此之外, TreeMap 還支持經過 descendingMap() 獲取和原來順序相反的 map。
若是 TreeMap 沒有使用自定義的 Comparator,則是不支持鍵爲 null 的,由於調用 compareTo() 可能會發生異常;若是自定義的比較器能夠接受 null 做爲參數,那麼是能夠支持將 null 做爲鍵的。
TreeMap 不是線程安全的,多線程狀況下要手動進行同步或使用 SortedMap m = Collections.synchronizedSortedMap(new TreeMap(...));
。