Java 容器源碼分析之 TreeMap

TreeMap 是一種基於紅黑樹實現的 Key-Value 結構。在使用集合視圖在 HashMap 中迭代時,是不能保證迭代順序的; LinkedHashMap 使用了雙向鏈表,保證按照插入順序或者訪問順序進行迭代。可是有些時候,咱們可能須要按照鍵的大小進行按序迭代,或者在使用哈希表的同時但願按鍵值進行排序,這個時候 TreeMap 就有其用武之地了。 TreeMap 支持按鍵值進行升序訪問,或者由傳入的比較器(Comparator)來控制。html

下面基於 JDK 8 的源碼對 TreeMap 進行一個簡單的分析。java

1
2
3
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable

同 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
2
3
4
5
6
7
8
9
10
11
//比較器,沒有指定的話默認使用Key的天然序
private final Comparator<? super K> comparator;

//紅黑樹根節點
private transient Entry<K,V> root;

//樹中節點的數量
private transient int size = 0;

//結構化修改的次數
private transient int modCount = 0;

TreeMap 一樣不是線程安全的,基於結構化修改的次數來實現 fail-fast 機制。於是要在多線程環境下使用時,可能須要手動進行同步,或者使用 Collections.synchronizedSortedMap 進行包裝。

TreeMap 中的紅黑樹使用的是「算法導論」中的實現,除了左右連接、紅黑標識之外,還有一個指向父節點的鏈接。紅黑樹的具體插入及刪除細節這裏不做過多的解釋,更深刻的細節能夠參考「算法導論」一書,不過建議先看一下 Sedgewick 的講解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//Entry (紅黑樹節點的定義)
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;//顏色,指向該節點的連接的顏色

/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}

/**
* Returns the key.
*
* @return the key
*/
public K getKey() {
return key;
}

/**
* Returns the value associated with the key.
*
* @return the value associated with the key
*/
public V getValue() {
return value;
}

/**
* Replaces the value currently associated with the key with the given
* value.
*
* @return the value associated with the key before this method was
* called
*/
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}

public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
//Key 和 Value都要 equals
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}

//哈希值的計算,Key和Value的哈希值進行位異或
public int hashCode() {
int keyHash = (key==null ? 0 : key.hashCode());
int valueHash = (value==null ? 0 : value.hashCode());
return keyHash ^ valueHash;
}

public String toString() {
return key + "=" + value;
}
}

添加及更新操做

爲了維持有序,添加及更新的代價較高,複雜度爲 O(log(n)) 。插入節點後須要修復紅黑樹,使其恢復平衡狀態,該操做在此不做介紹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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); //Key 存在,更新value
} while (t != null);
}
else { //比較器爲null,Key 必須實現 Comparable 接口
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); //Key 存在,更新value
} while (t != null);
}
//Key 不存在,新建節點,插入二叉樹
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;
}

刪除

從紅黑樹中刪除一個節點比插入更爲複雜,這裏不做展開。

1
2
3
4
5
6
7
8
9
public V remove(Object key) {
Entry<K,V> p = getEntry(key); //先查找該節點
if (p == null)
return null;

V oldValue = p.value;
deleteEntry(p); //刪除節點
return oldValue;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private void deleteEntry(Entry<K,V> p) {
modCount++; //刪除使得結構發生變化
size--;

// If strictly internal, copy successor's element to p and then make p
// point to successor.
// 被刪除節點的左右子樹都不爲空
if (p.left != null && p.right != null) {
//用後繼節點代替當前節點
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children

// Start fixup at replacement node, if it exists.
// 左子節點存在,則 replacement 爲左子節點,不然爲右子節點
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) //p 就是根節點
root = replacement;
else if (p == p.parent.left)//p 是父節點的左子節點
p.parent.left = replacement;
else//p 是父節點的右子節點
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;
}
}
}

查找

紅黑樹也是排序二叉樹,按照排序二叉樹的查找方法進行查找。複雜度爲 O(log(n)) 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public V get(Object key) {
Entry<K,V> p = getEntry(key);
return (p==null ? null : p.value);
}

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) {
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) {
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;
}

判斷是否包含 key 或 value :

1
2
3
4
5
6
7
8
9
10
11
public boolean containsKey(Object key) {
return getEntry(key) != null;
}

public boolean containsValue(Object value) {
//從第一個節點開始,不斷查找後繼節點
for (Entry<K,V> e = getFirstEntry(); e != null; e = successor(e))
if (valEquals(value, e.value))
return true;
return false;
}

導航方法

NaviableMap 接口支持一系列的導航方法,有 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry()、 pollFirstEntry() 、 pollLastEntry() 等,它們的實現原理都是相似的,區別在於如何在排序的二叉樹中查找到對應的節點。

以 lowerEntry() 和 floorEntry() 爲例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//小於給定的Key
public Map.Entry<K,V> lowerEntry(K key) {
return exportEntry(getLowerEntry(key));
}

final Entry<K,V> getLowerEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
//1. 若是節點 p 小於 key
if (cmp > 0) {
//1.1 節點 p 有右子樹,則在右子樹中搜索
if (p.right != null)
p = p.right;
//1.2 節點 p 沒有右子樹,找到目標
else
return p;
//2. 節點 p 大於等於 key
} else {
//2.1 節點 p 有左子樹,則在左子樹中繼續搜索
if (p.left != null) {
p = p.left;
//2.2 節點 p 無左子樹,找出 p 的前驅節點,並返回
//前驅節點要麼不存在,要麼就是小於 key 的最大節點
//由於從根節點一直遍歷到 p,那麼以前通過的全部節點都是大於等於 key 的
//且 p 沒有左子樹,即 p 是大於等於 key 的全部節點中最小的
//則 p 的前驅必定是查找的目標
} 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;
}

public K lowerKey(K key) {
return keyOrNull(getLowerEntry(key));
}

//小於等於
public Map.Entry<K,V> floorEntry(K key) {
return exportEntry(getFloorEntry(key));
}

//和 getLowerEntry 相似,相等時的處理不一樣
final Entry<K,V> getFloorEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp > 0) {
if (p.right != null)
p = p.right;
else
return p;
} else if (cmp < 0) {
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;
}
} else
return p;

}
return null;
}

查找的過程能夠和前驅節點的方法進行類比。 TreeMap 並無直接暴露 getLowerEntry() 方法,而是使用 exportEntry(getLowerEntry(key)) 進行了一次包裝。看似「畫蛇添足」,其實是爲了防止對節點進行修改。SimpleImmutableEntry 類能夠看做不可修改的 Key-Value 對,由於成員變量 key 和 value 都是 final 的。

即經過暴露出來的接口 firstEntry()、 lastEntry()、 lowerEntry()、 higherEntry()、 floorEntry()、 ceilingEntry() 是不能夠修改獲取的節點的,不然會拋出異常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Return SimpleImmutableEntry for entry, or null if null
*/
static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
return (e == null) ? null :
new AbstractMap.SimpleImmutableEntry<>(e);
}

//AbstractMap.SimpleImmutableEntry
public static class SimpleImmutableEntry<K,V>
implements Entry<K,V>, java.io.Serializable
{
private static final long serialVersionUID = 7138329143949025153L;

private final K key;
private final V value;

public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}

public V setValue(V value) {
throw new UnsupportedOperationException();
}
//....
//
}

pollFirstEntry() 、 pollLastEntry() 獲取第一個和最後一個節點,並將它們從紅黑樹中刪除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Map.Entry<K,V> pollFirstEntry() {
Entry<K,V> p = getFirstEntry();
Map.Entry<K,V> result = exportEntry(p);
if (p != null)
deleteEntry(p);
return result;
}

public Map.Entry<K,V> pollLastEntry() {
Entry<K,V> p = getLastEntry();
Map.Entry<K,V> result = exportEntry(p);
if (p != null)
deleteEntry(p);
return result;
}

遍歷

能夠按照鍵的順序遍歷對 TreeSet 進行遍歷,由於底層使用了紅黑樹來保證有序性,迭代器的實現就是按序訪問排序二叉樹中的節點。

先看一些內部抽象類 PrivateEntryIterator ,它是 TreeMap 中全部迭代器的基礎:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
abstract class PrivateEntryIterator<T> implements Iterator<T> {
Entry<K,V> next;
Entry<K,V> lastReturned;
int expectedModCount;

PrivateEntryIterator(Entry<K,V> first) {
expectedModCount = modCount;
lastReturned = null;
next = first;
}

public final boolean hasNext() {
return next != null;
}

final Entry<K,V> nextEntry() {
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
next = successor(e); //後繼節點
lastReturned = e;
return e;
}

final Entry<K,V> prevEntry() {
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
next = predecessor(e); //前驅節點
lastReturned = e;
return e;
}

public void remove() {
if (lastReturned == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
// deleted entries are replaced by their successors
if (lastReturned.left != null && lastReturned.right != null)
next = lastReturned;
deleteEntry(lastReturned);
expectedModCount = modCount;
lastReturned = null;
}
}

由於紅黑樹自身就是有序的,迭代是隻要從第一個節點不斷獲取後繼節點便可。固然,逆序時則是從最後一個節點不斷獲取前驅節點。經過迭代器訪問時基於 modCount 實現對併發修改的檢查。

在排序二叉樹中獲取前驅和後繼節點的方法以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//後繼節點
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
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 {
//右子樹不存在
//若父節點爲null,則該節點是最大節點(根節點,且無右子樹),無後繼,返回null
//若當前節點是父節點的左子節點,直接返回父節點
//若當前節點是父節點的右子節點,則當前節點是以父節點爲根的子樹中最大的節點
Entry<K,V> p = t.parent; //父節點
Entry<K,V> ch = t;//當前節點
while (p != null && ch == p.right) {
//是右子節點,向上迭代,直到是左子節點
ch = p;
p = p.parent;
}
return p;
}
}

//前驅節點,同後繼節點處理邏輯一致,左右顛倒
static <K,V> Entry<K,V> predecessor(Entry<K,V> t) {
if (t == null)
return null;
else if (t.left != null) {
//左子樹存在,則取左子樹的最小節點
Entry<K,V> p = t.left;
while (p.right != null)
p = p.right;
return p;
} else {
//左子樹不存在
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.left) {
ch = p;
p = p.parent;
}
return p;
}
}

其它方法

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(...));

相關文章
相關標籤/搜索