1. 程式人生 > >Java 集合系列12之 TreeMap詳細介紹(源碼解析)和使用示例

Java 集合系列12之 TreeMap詳細介紹(源碼解析)和使用示例

叠代器 opera java port fail 繼承關系 關於 就是 開始

第1部分 TreeMap介紹
TreeMap 簡介

TreeMap 是一個有序的key-value集合,它是通過紅黑樹實現的。
TreeMap 繼承於AbstractMap,所以它是一個Map,即一個key-value集合。
TreeMap 實現了NavigableMap接口,意味著它支持一系列的導航方法。比如返回有序的key集合。
TreeMap 實現了Cloneable接口,意味著它能被克隆。
TreeMap 實現了java.io.Serializable接口,意味著它支持序列化。

TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的自然順序進行排序,或者根據創建映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。

TreeMap的基本操作 containsKey、get、put 和 remove 的時間復雜度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的叠代器是fail-fastl的。

TreeMap的構造函數

復制代碼
// 默認構造函數。使用該構造函數,TreeMap中的元素按照自然排序進行排列。
TreeMap()

// 創建的TreeMap包含Map
TreeMap(Map<? extends K, ? extends V> copyFrom)

// 指定Tree的比較器
TreeMap(Comparator<? super K> comparator)

// 創建的TreeSet包含copyFrom
TreeMap(SortedMap<K, ? extends V> copyFrom)
復制代碼

TreeMap的API

復制代碼
Entry<K, V> ceilingEntry(K key)
K ceilingKey(K key)
void clear()
Object clone()
Comparator<? super K> comparator()

boolean containsKey(Object key)
NavigableSet<K> descendingKeySet()
NavigableMap<K, V> descendingMap()
Set<Entry<K, V>> entrySet()
Entry<K, V> firstEntry()
K firstKey()
Entry<K, V> floorEntry(K key)
K floorKey(K key)
V get(Object key)
NavigableMap<K, V> headMap(K to, boolean inclusive)
SortedMap<K, V> headMap(K toExclusive)
Entry<K, V> higherEntry(K key)
K higherKey(K key)
boolean isEmpty()
Set<K> keySet()
Entry<K, V> lastEntry()
K lastKey()
Entry<K, V> lowerEntry(K key)
K lowerKey(K key)
NavigableSet<K> navigableKeySet()
Entry<K, V> pollFirstEntry()
Entry<K, V> pollLastEntry()
V put(K key, V value)
V remove(Object key)
int size()
SortedMap<K, V> subMap(K fromInclusive, K toExclusive)
NavigableMap<K, V> subMap(K from, boolean fromInclusive, K to, boolean toInclusive)
NavigableMap<K, V> tailMap(K from, boolean inclusive)
SortedMap<K, V> tailMap(K fromInclusive)
復制代碼

第2部分 TreeMap數據結構
TreeMap的繼承關系

復制代碼
java.lang.Object
? java.util.AbstractMap<K, V>
? java.util.TreeMap<K, V>

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable {}
復制代碼

TreeMap與Map關系如下圖:

技術分享圖片

從圖中可以看出:
(01) TreeMap實現繼承於AbstractMap,並且實現了NavigableMap接口。
(02) TreeMap的本質是R-B Tree(紅黑樹),它包含幾個重要的成員變量: root, size, comparator。
  root 是紅黑數的根節點。它是Entry類型,Entry是紅黑數的節點,它包含了紅黑數的6個基本組成成分:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色)。Entry節點根據key進行排序,Entry節點包含的內容為value。
  紅黑數排序時,根據Entry中的key進行排序;Entry中的key比較大小是根據比較器comparator來進行判斷的。
  size是紅黑數中節點的個數。

關於紅黑數的具體算法,請參考"紅黑樹(一) 原理和算法詳細介紹"。

第3部分 TreeMap源碼解析(基於JDK1.6.0_45)
為了更了解TreeMap的原理,下面對TreeMap源碼代碼作出分析。我們先給出源碼內容,後面再對源碼進行詳細說明,當然,源碼內容中也包含了詳細的代碼註釋。讀者閱讀的時候,建議先看後面的說明,先建立一個整體印象;之後再閱讀源碼。

View Code
說明:

在詳細介紹TreeMap的代碼之前,我們先建立一個整體概念。
TreeMap是通過紅黑樹實現的,TreeMap存儲的是key-value鍵值對,TreeMap的排序是基於對key的排序。
TreeMap提供了操作“key”、“key-value”、“value”等方法,也提供了對TreeMap這顆樹進行整體操作的方法,如獲取子樹、反向樹。
後面的解說內容分為幾部分,
首先,介紹TreeMap的核心,即紅黑樹相關部分;
然後,介紹TreeMap的主要函數;
再次,介紹TreeMap實現的幾個接口;
最後,補充介紹TreeMap的其它內容。

TreeMap本質上是一顆紅黑樹。要徹底理解TreeMap,建議讀者先理解紅黑樹。關於紅黑樹的原理,可以參考:紅黑樹(一) 原理和算法詳細介紹

第3.1部分 TreeMap的紅黑樹相關內容

TreeMap中於紅黑樹相關的主要函數有:
1 數據結構
1.1 紅黑樹的節點顏色--紅色

private static final boolean RED = false;
1.2 紅黑樹的節點顏色--黑色

private static final boolean BLACK = true;
1.3 “紅黑樹的節點”對應的類。

static final class Entry<K,V> implements Map.Entry<K,V> { ... }
Entry包含了6個部分內容:key(鍵)、value(值)、left(左孩子)、right(右孩子)、parent(父節點)、color(顏色)
Entry節點根據key進行排序,Entry節點包含的內容為value。

2 相關操作

2.1 左旋

private void rotateLeft(Entry<K,V> p) { ... }
2.2 右旋

private void rotateRight(Entry<K,V> p) { ... }
2.3 插入操作

public V put(K key, V value) { ... }
2.4 插入修正操作
紅黑樹執行插入操作之後,要執行“插入修正操作”。
目的是:保紅黑樹在進行插入節點之後,仍然是一顆紅黑樹

private void fixAfterInsertion(Entry<K,V> x) { ... }
2.5 刪除操作

private void deleteEntry(Entry<K,V> p) { ... }
2.6 刪除修正操作

紅黑樹執行刪除之後,要執行“刪除修正操作”。
目的是保證:紅黑樹刪除節點之後,仍然是一顆紅黑樹

private void fixAfterDeletion(Entry<K,V> x) { ... }
關於紅黑樹部分,這裏主要是指出了TreeMap中那些是紅黑樹的主要相關內容。具體的紅黑樹相關操作API,這裏沒有詳細說明,因為它們僅僅只是將算法翻譯成代碼。讀者可以參考“紅黑樹(一) 原理和算法詳細介紹”進行了解。

第3.2部分 TreeMap的構造函數

1 默認構造函數

使用默認構造函數構造TreeMap時,使用java的默認的比較器比較Key的大小,從而對TreeMap進行排序。

public TreeMap() {
comparator = null;
}
2 帶比較器的構造函數

public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
3 帶Map的構造函數,Map會成為TreeMap的子集

public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
該構造函數會調用putAll()將m中的所有元素添加到TreeMap中。putAll()源碼如下:

public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
從中,我們可以看出putAll()就是將m中的key-value逐個的添加到TreeMap中。

4 帶SortedMap的構造函數,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) {
}
}
復制代碼
該構造函數不同於上一個構造函數,在上一個構造函數中傳入的參數是Map,Map不是有序的,所以要逐個添加。
而該構造函數的參數是SortedMap是一個有序的Map,我們通過buildFromSorted()來創建對應的Map。
buildFromSorted涉及到的代碼如下:

View Code
要理解buildFromSorted,重點說明以下幾點:

第一,buildFromSorted是通過遞歸將SortedMap中的元素逐個關聯。
第二,buildFromSorted返回middle節點(中間節點)作為root。
第三,buildFromSorted添加到紅黑樹中時,只將level == redLevel的節點設為紅色。第level級節點,實際上是buildFromSorted轉換成紅黑樹後的最底端(假設根節點在最上方)的節點;只將紅黑樹最底端的階段著色為紅色,其余都是黑色。

第3.3部分 TreeMap的Entry相關函數

TreeMap的 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry() 原理都是類似的;下面以firstEntry()來進行詳細說明

我們先看看firstEntry()和getFirstEntry()的代碼:

復制代碼
public Map.Entry<K,V> firstEntry() {
return exportEntry(getFirstEntry());
}

final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
復制代碼
從中,我們可以看出 firstEntry() 和 getFirstEntry() 都是用於獲取第一個節點。
但是,firstEntry() 是對外接口; getFirstEntry() 是內部接口。而且,firstEntry() 是通過 getFirstEntry() 來實現的。那為什麽外界不能直接調用 getFirstEntry(),而需要多此一舉的調用 firstEntry() 呢?
先告訴大家原因,再進行詳細說明。這麽做的目的是:防止用戶修改返回的Entry。getFirstEntry()返回的Entry是可以被修改的,但是經過firstEntry()返回的Entry不能被修改,只可以讀取Entry的key值和value值。下面我們看看到底是如何實現的。
(01) getFirstEntry()返回的是Entry節點,而Entry是紅黑樹的節點,它的源碼如下:

復制代碼
// 返回“紅黑樹的第一個節點”
final Entry<K,V> getFirstEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
復制代碼
從中,我們可以調用Entry的getKey()、getValue()來獲取key和value值,以及調用setValue()來修改value的值。

(02) firstEntry()返回的是exportEntry(getFirstEntry())。下面我們看看exportEntry()幹了些什麽?

static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
return e == null? null :
new AbstractMap.SimpleImmutableEntry<K,V>(e);
}
實際上,exportEntry() 是新建一個AbstractMap.SimpleImmutableEntry類型的對象,並返回。

SimpleImmutableEntry的實現在AbstractMap.java中,下面我們看看AbstractMap.SimpleImmutableEntry是如何實現的,代碼如下:

View Code
從中,我們可以看出SimpleImmutableEntry實際上是簡化的key-value節點。
它只提供了getKey()、getValue()方法類獲取節點的值;但不能修改value的值,因為調用 setValue() 會拋出異常UnsupportedOperationException();

再回到我們之前的問題:那為什麽外界不能直接調用 getFirstEntry(),而需要多此一舉的調用 firstEntry() 呢?
現在我們清晰的了解到:
(01) firstEntry()是對外接口,而getFirstEntry()是內部接口。
(02) 對firstEntry()返回的Entry對象只能進行getKey()、getValue()等讀取操作;而對getFirstEntry()返回的對象除了可以進行讀取操作之後,還可以通過setValue()修改值。

第3.4部分 TreeMap的key相關函數

TreeMap的firstKey()、lastKey()、lowerKey()、higherKey()、floorKey()、ceilingKey()原理都是類似的;下面以ceilingKey()來進行詳細說明

ceilingKey(K key)的作用是“返回大於/等於key的最小的鍵值對所對應的KEY,沒有的話返回null”,它的代碼如下:

public K ceilingKey(K key) {
return keyOrNull(getCeilingEntry(key));
}
ceilingKey()是通過getCeilingEntry()實現的。keyOrNull()的代碼很簡單,它是獲取節點的key,沒有的話,返回null。

static <K,V> K keyOrNull(TreeMap.Entry<K,V> e) {
return e == null? null : e.key;
}
getCeilingEntry(K key)的作用是“獲取TreeMap中大於/等於key的最小的節點,若不存在(即TreeMap中所有節點的鍵都比key大),就返回null”。它的實現代碼如下:

View Code

第3.5部分 TreeMap的values()函數

values() 返回“TreeMap中值的集合”

values()的實現代碼如下:

public Collection<V> values() {
Collection<V> vs = values;
return (vs != null) ? vs : (values = new Values());
}
說明:從中,我們可以發現values()是通過 new Values() 來實現 “返回TreeMap中值的集合”。

那麽Values()是如何實現的呢? 沒錯!由於返回的是值的集合,那麽Values()肯定返回一個集合;而Values()正好是集合類Value的構造函數。Values繼承於AbstractCollection,它的代碼如下:

View Code
說明:從中,我們可以知道Values類就是一個集合。而 AbstractCollection 實現了除 size() 和 iterator() 之外的其它函數,因此只需要在Values類中實現這兩個函數即可。
size() 的實現非常簡單,Values集合中元素的個數=該TreeMap的元素個數。(TreeMap每一個元素都有一個值嘛!)
iterator() 則返回一個叠代器,用於遍歷Values。下面,我們一起可以看看iterator()的實現:

public Iterator<V> iterator() {
return new ValueIterator(getFirstEntry());
}
說明: iterator() 是通過ValueIterator() 返回叠代器的,ValueIterator是一個類。代碼如下:

復制代碼
final class ValueIterator extends PrivateEntryIterator<V> {
ValueIterator(Entry<K,V> first) {
super(first);
}
public V next() {
return nextEntry().value;
}
}
復制代碼
說明:ValueIterator的代碼很簡單,它的主要實現應該在它的父類PrivateEntryIterator中。下面我們一起看看PrivateEntryIterator的代碼:

View Code
說明:PrivateEntryIterator是一個抽象類,它的實現很簡單,只只實現了Iterator的remove()和hasNext()接口,沒有實現next()接口。
而我們在ValueIterator中已經實現的next()接口。
至此,我們就了解了iterator()的完整實現了。

第3.6部分 TreeMap的entrySet()函數

entrySet() 返回“鍵值對集合”。顧名思義,它返回的是一個集合,集合的元素是“鍵值對”。

下面,我們看看它是如何實現的?entrySet() 的實現代碼如下:

public Set<Map.Entry<K,V>> entrySet() {
EntrySet es = entrySet;
return (es != null) ? es : (entrySet = new EntrySet());
}
說明:entrySet()返回的是一個EntrySet對象。

下面我們看看EntrySet的代碼:

View Code
說明:
EntrySet是“TreeMap的所有鍵值對組成的集合”,而且它單位是單個“鍵值對”。
EntrySet是一個集合,它繼承於AbstractSet。而AbstractSet實現了除size() 和 iterator() 之外的其它函數,因此,我們重點了解一下EntrySet的size() 和 iterator() 函數

size() 的實現非常簡單,AbstractSet集合中元素的個數=該TreeMap的元素個數。
iterator() 則返回一個叠代器,用於遍歷AbstractSet。從上面的源碼中,我們可以發現iterator() 是通過EntryIterator實現的;下面我們看看EntryIterator的源碼:

復制代碼
final class EntryIterator extends PrivateEntryIterator<Map.Entry<K,V>> {
EntryIterator(Entry<K,V> first) {
super(first);
}
public Map.Entry<K,V> next() {
return nextEntry();
}
}
復制代碼
說明:和Values類一樣,EntryIterator也繼承於PrivateEntryIterator類。

第3.7部分 TreeMap實現的Cloneable接口

TreeMap實現了Cloneable接口,即實現了clone()方法。
clone()方法的作用很簡單,就是克隆一個TreeMap對象並返回。

View Code

第3.8部分 TreeMap實現的Serializable接口

TreeMap實現java.io.Serializable,分別實現了串行讀取、寫入功能。
串行寫入函數是writeObject(),它的作用是將TreeMap的“容量,所有的Entry”都寫入到輸出流中。
而串行讀取函數是readObject(),它的作用是將TreeMap的“容量、所有的Entry”依次讀出。
readObject() 和 writeObject() 正好是一對,通過它們,我能實現TreeMap的串行傳輸。

View Code
說到這裏,就順便說一下“關鍵字transient”的作用

transient是Java語言的關鍵字,它被用來表示一個域不是該對象串行化的一部分。
Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想用serialization機制來保存它。為了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。
當一個對象被串行化的時候,transient型變量的值不包括在串行化的表示中,然而非transient型的變量是被包括進去的。

第3.9部分 TreeMap實現的NavigableMap接口

firstKey()、lastKey()、lowerKey()、higherKey()、ceilingKey()、floorKey();
firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry();
上面已經講解過這些API了,下面對其它的API進行說明。

1 反向TreeMap
descendingMap() 的作用是返回當前TreeMap的反向的TreeMap。所謂反向,就是排序順序和原始的順序相反。

我們已經知道TreeMap是一顆紅黑樹,而紅黑樹是有序的。
TreeMap的排序方式是通過比較器,在創建TreeMap的時候,若指定了比較器,則使用該比較器;否則,就使用Java的默認比較器。
而獲取TreeMap的反向TreeMap的原理就是將比較器反向即可!

理解了descendingMap()的反向原理之後,再講解一下descendingMap()的代碼。

復制代碼
// 獲取TreeMap的降序Map
public NavigableMap<K, V> descendingMap() {
NavigableMap<K, V> km = descendingMap;
return (km != null) ? km :
(descendingMap = new DescendingSubMap(this,
true, null, true,
true, null, true));
}
復制代碼
從中,我們看出descendingMap()實際上是返回DescendingSubMap類的對象。下面,看看DescendingSubMap的源碼:

View Code
從中,我們看出DescendingSubMap是降序的SubMap,它的實現機制是將“SubMap的比較器反轉”。

它繼承於NavigableSubMap。而NavigableSubMap是一個繼承於AbstractMap的抽象類;它包括2個子類——"(升序)AscendingSubMap"和"(降序)DescendingSubMap"。NavigableSubMap為它的兩個子類實現了許多公共API。
下面看看NavigableSubMap的源碼。

View Code
NavigableSubMap源碼很多,但不難理解;讀者可以通過源碼和註釋進行理解。

其實,讀完NavigableSubMap的源碼後,我們可以得出它的核心思想是:它是一個抽象集合類,為2個子類——"(升序)AscendingSubMap"和"(降序)DescendingSubMap"而服務;因為NavigableSubMap實現了許多公共API。它的最終目的是實現下面的一系列函數:

復制代碼
headMap(K toKey, boolean inclusive)
headMap(K toKey)
subMap(K fromKey, K toKey)
subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)
tailMap(K fromKey)
tailMap(K fromKey, boolean inclusive)
navigableKeySet()
descendingKeySet()
復制代碼

第3.10部分 TreeMap其它函數

1 順序遍歷和逆序遍歷

TreeMap的順序遍歷和逆序遍歷原理非常簡單。
由於TreeMap中的元素是從小到大的順序排列的。因此,順序遍歷,就是從第一個元素開始,逐個向後遍歷;而倒序遍歷則恰恰相反,它是從最後一個元素開始,逐個往前遍歷。

我們可以通過 keyIterator() 和 descendingKeyIterator()來說明!
keyIterator()的作用是返回順序的KEY的集合,
descendingKeyIterator()的作用是返回逆序的KEY的集合。

keyIterator() 的代碼如下:

Iterator<K> keyIterator() {
return new KeyIterator(getFirstEntry());
}
說明:從中我們可以看出keyIterator() 是返回以“第一個節點(getFirstEntry)” 為其實元素的叠代器。
KeyIterator的代碼如下:

復制代碼
final class KeyIterator extends PrivateEntryIterator<K> {
KeyIterator(Entry<K,V> first) {
super(first);
}
public K next() {
return nextEntry().key;
}
}
復制代碼
說明:KeyIterator繼承於PrivateEntryIterator。當我們通過next()不斷獲取下一個元素的時候,就是執行的順序遍歷了。

descendingKeyIterator()的代碼如下:

Iterator<K> descendingKeyIterator() {
return new DescendingKeyIterator(getLastEntry());
}
說明:從中我們可以看出descendingKeyIterator() 是返回以“最後一個節點(getLastEntry)” 為其實元素的叠代器。
再看看DescendingKeyIterator的代碼:

復制代碼
final class DescendingKeyIterator extends PrivateEntryIterator<K> {
DescendingKeyIterator(Entry<K,V> first) {
super(first);
}
public K next() {
return prevEntry().key;
}
}
復制代碼
說明:DescendingKeyIterator繼承於PrivateEntryIterator。當我們通過next()不斷獲取下一個元素的時候,實際上調用的是prevEntry()獲取的上一個節點,這樣它實際上執行的是逆序遍歷了。

至此,TreeMap的相關內容就全部介紹完畢了。若有錯誤或紕漏的地方,歡迎指正!

第4部分 TreeMap遍歷方式
4.1 遍歷TreeMap的鍵值對

第一步:根據entrySet()獲取TreeMap的“鍵值對”的Set集合。
第二步:通過Iterator叠代器遍歷“第一步”得到的集合。

復制代碼
// 假設map是TreeMap對象
// map中的key是String類型,value是Integer類型
Integer integ = null;
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
// 獲取key
key = (String)entry.getKey();
// 獲取value
integ = (Integer)entry.getValue();
}
復制代碼

4.2 遍歷TreeMap的鍵

第一步:根據keySet()獲取TreeMap的“鍵”的Set集合。
第二步:通過Iterator叠代器遍歷“第一步”得到的集合。

復制代碼
// 假設map是TreeMap對象
// map中的key是String類型,value是Integer類型
String key = null;
Integer integ = null;
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
// 獲取key
key = (String)iter.next();
// 根據key,獲取value
integ = (Integer)map.get(key);
}
復制代碼

4.3 遍歷TreeMap的值

第一步:根據value()獲取TreeMap的“值”的集合。
第二步:通過Iterator叠代器遍歷“第一步”得到的集合。

復制代碼
// 假設map是TreeMap對象
// map中的key是String類型,value是Integer類型
Integer value = null;
Collection c = map.values();
Iterator iter= c.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
復制代碼
TreeMap遍歷測試程序如下:

View Code

第5部分 TreeMap示例
下面通過實例來學習如何使用TreeMap

View Code
運行結果:

復制代碼
{one=8, three=4, two=2}
next : one - 8
next : three - 4
next : two - 2
size: 3
contains key two : true
contains key five : false
contains value 0 : false
tmap:{one=8, two=2}
tmap is empty
復制代碼

Java 集合系列12之 TreeMap詳細介紹(源碼解析)和使用示例