1.定義
TreeMap是基于紅黑樹的實現(xiàn),也是記錄了key-value的映射關(guān)系,該映射根據(jù)key的自然排序進行排序或者根據(jù)構(gòu)造方法中傳入的比較器進行排序,也就是說TreeMap是有序的key-value集合。
通過TreeMap的定義可以看出以下幾點:
1.TreeMap的內(nèi)部實現(xiàn)是紅黑樹實現(xiàn)的,關(guān)于紅黑樹的內(nèi)容可以參考JAVA學習-紅黑樹詳解
2.TreeMap是有序的key-value集合。
下面就通過源碼去了解下TreeMap的內(nèi)部實現(xiàn),本文源碼來自與JDK_1.8.0_131
2.結(jié)構(gòu)
2.1 類圖結(jié)構(gòu)
如上圖所示是TreeMap的類圖結(jié)構(gòu),其繼承、實現(xiàn)的接口如下所示:
- 1.Map 接口: 定義將鍵值映射到值的對象,Map規(guī)定不能包含重復的鍵值,每個鍵最多可以映射一個值,這個接口是用來替換Dictionary類。
- 2.AbstractMap 類: 提供了一個Map骨架的實現(xiàn),盡量減少了實現(xiàn)Map接口所需要的工作量
- 3.SortedMap 接口: 定義按照key排序的Map結(jié)構(gòu),規(guī)定key-value是根據(jù)鍵key值的自然排序進行排序的,或者根據(jù)構(gòu)造key-value時設定的構(gòu)造器進行排序。
- 4.NavigableMap 接口: 是SortedMap接口的子接口,在其基礎上擴展了針對搜索目標返回最近匹配項的導航方法,例如方法lowEntry、floorEntry、ceilingEntry等,如果不存在這樣的鍵,則返回null
- 5.Cloneable 接口: 實現(xiàn)了該接口的類可以顯示的調(diào)用Object.clone()方法,合法的對該類實例進行字段復制,如果沒有實現(xiàn)Cloneable接口的實例上調(diào)用Obejct.clone()方法,會拋出CloneNotSupportException異常。正常情況下,實現(xiàn)了Cloneable接口的類會以公共方法重寫Object.clone()
- 6.Serializable 接口: 實現(xiàn)了該接口標示了類可以被序列化和反序列化,具體的 查詢序列化詳解
2.2 構(gòu)造方法及基本屬性
2.2.1 構(gòu)造方法
如下TreeMap提供了如下的構(gòu)造方法:
- public TreeMap() :空的構(gòu)造方法,默認容量為0
- public TreeMap(Comparator<? super K> comparator):需要一個Comparator比較器作為參數(shù),其實這一點很好理解因為TreeMap是一個有序的key-value集合,所以可以自定義比較器進行設定元素的排序規(guī)則
- public TreeMap(Map<? extends K, ? extends V> m):將一個Map構(gòu)建成一個TreeMap
- public TreeMap(SortedMap<K, ? extends V> m):將一個SortedMap構(gòu)建成一個TreeMap
2.2.2 基本屬性
如下代碼所示,是TreeMap的基本屬性,其實TreeMap的屬性相對來說比較簡單如下所示,如下也有Entry節(jié)點的源碼。
//比較器
private final Comparator<? super K> comparator;
// Entry節(jié)點,這個表示紅黑樹的根節(jié)點
private transient Entry<K,V> root;
// TreeMap中元素的個數(shù)
private transient int size = 0;
// TreeMap修改次數(shù)
private transient int modCount = 0;
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;//對于key值
V value;//對于value值
Entry<K,V> left;//指向左子樹的引用
Entry<K,V> right;//指向右子樹的引用
Entry<K,V> parent;//指向父節(jié)點的引用
boolean color = BLACK;//節(jié)點的顏色默認是黑色
// 省略部分代碼
}
如下圖所示,是TreeMap(key:為[4,2,5,6,8,7,9])的一個內(nèi)部結(jié)構(gòu)示意圖,其中每個節(jié)點都是Entry類型的
3.實現(xiàn)
下面會詳細介紹TreeMap中的put(K key,V value)方法及get(Object key)方法
3.1 put方法
public V put(K key, V value) {
Entry<K,V> t = root;
//根節(jié)點為空直接新增節(jié)點,否則繼續(xù)下面的流程
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;
//有特殊指定的比較器則使用比較器進行比較,否則使用key的比較器進行比較
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);
} while (t != null);
}
else {
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);
} while (t != null);
}
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;
}
如上所示為put方法的源碼,可能看起來相對來說比較長,但是我們來詳細分析下如以下步驟
- 1.校驗根節(jié)點:校驗根節(jié)點是否為空,若為空則根據(jù)傳入的key-value的值創(chuàng)建一個新的節(jié)點,若根節(jié)點不為空則繼續(xù)第二步
- 2.尋找插入位置:其實這點很好理解,由于TreeMap內(nèi)部是紅黑樹實現(xiàn)的則插入元素時,實際上是會去遍歷左子樹,或者右子樹(具體遍歷哪顆子樹是根據(jù)當前插入key-value與根節(jié)點的比較判定的,這部在代碼里面其實分為兩步來體現(xiàn)是否指定比較器,若指定了則使用指定的比較器比較,否則使用默認key的比較器進行比較(這里有一點需要注意是TreeMap是不允許key-value為NULL)
- 3.新建并恢復:在第二步中實際上是需要確定當前插入節(jié)點的位置,而這一步是實際的插入操作,而插入之后為啥還需要調(diào)用fixAfterInsertion方法,這里是因為紅黑樹插入一個節(jié)點后可能會破壞紅黑樹的性質(zhì),則通過修改的代碼使得紅黑樹從新達到平衡,具體可以參考JAVA學習-紅黑樹詳解
其實從上面的分析來講TreeMap插入一個節(jié)點的流程不是太復雜,只要深刻的理解紅黑樹的原理,這個流程就相對簡單很多,其實這里本人存在一點疑問(為什么要引入紅黑樹來做TreeMap的結(jié)構(gòu),需要看到本文的大神指導指導),
3.2 get方法
如下所示是get方法的源碼,其實際是調(diào)用了getEntry方法
public V get(Object key) {
//獲取元素,若為空則返回null否則返回其值
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)
//構(gòu)造器不為空則調(diào)用以下方法
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;
}
getEntry方法主要流程如下:
- 1.構(gòu)造器校驗:判斷是否指定構(gòu)造器,若指定則調(diào)用getEntryUsingComparator,若沒有則進行第二步
- 2.空值校驗:key若為空直接拋出NullPointerException,從這點可以看出TreeMap是不允許Key-value為空的
- 3.遍歷返回:遍歷整個紅黑樹若找到對應的值則返回,否則返回null值
以上就是getEntry方法的主要流程,在步驟一中提到了getEntryUsingComparator方法,其實該方法與步驟三中的操作并無太大差異,存在的區(qū)別就是使用了構(gòu)造器進行比較。
3.總結(jié)
本文主要介紹了TreeMap的實現(xiàn)及其特點,還有一些方法及代碼沒有詳解介紹,若想深入了解可以查看源碼做更深入的探究。若有問題,請指正。
- 1.TreeMap的內(nèi)部實現(xiàn)是紅黑樹,關(guān)于紅黑樹的詳解請查看JAVA學習-紅黑樹詳解
- 2.TreeMap特點有些是源于紅黑樹的一些特點,比如TreeMap是有序的,TreeMap不允許key-value為空等