TreeMap原始碼分析(jdk1.8)
TreeMap的基本概念:
- TreeMap集合是基於紅黑樹(Red-Black tree)的 NavigableMap實現。該集合最重要的特點就是可排序,該對映根據其鍵的自然順序進行排序,或者根據建立對映時提供的 Comparator 進行排序,具體取決於使用的構造方法。這句話是什麼意思呢?就是說TreeMap可以對新增進來的元素進行排序,可以按照預設的排序方式,也可以自己指定排序方式。
- 根據上一條,我們要想使用TreeMap儲存並排序我們自定義的類(如User類),那麼必須自己定義比較機制:一種方式是User類去實現Java.lang.Comparable介面,並實現其compareTo()方法。另一種方式是寫一個類(如MyCompatator)去實現java.util.Comparator介面,並實現compare()方法,然後將MyCompatator類例項物件作為TreeMap的構造方法引數進行傳參。
- TreeMap的實現是紅黑樹演算法的實現,應該瞭解紅黑樹的基本概念。
一、紅黑樹簡介
紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具體二叉樹所有的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。
1、每個節點都只能是紅色或者黑色
2、根節點是黑色
3、每個葉節點(NIL節點,空節點)是黑色的。
4、如果一個結點是紅的,則它兩個子節點都是黑的。也就是說在一條路徑上不能出現相鄰的兩個紅色結點。
5、從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。
具體實現參考:http://www.cnblogs.com/fanzhidongyzby/p/3187912.html
http://blog.csdn.net/eric491179912/article/details/6179908
/************************************TreeMap********************************/
TreeMap的定義如下:
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
TreeMap繼承AbstractMap,實現NavigableMap、Cloneable、Serializable三個介面。其中AbstractMap表明TreeMap為一個Map即支援key-value的集合, NavigableMap(更多)則意味著它支援一系列的導航方法,具備針對給定搜尋目標返回最接近匹配項的導航方法 。
TreeMap中同時也包含了如下幾個重要的屬性:
//比較器,因為TreeMap是有序的,通過comparator介面我們可以對TreeMap的內部排序進行精密的控制
private final Comparator<? super K> comparator;
//TreeMap紅-黑節點,為TreeMap的內部類
private transient Entry<K,V> root = null;
//容器大小
private transient int size = 0;
//TreeMap修改次數
private transient int modCount = 0;
//紅黑樹的節點顏色--紅色
private static final boolean RED = false;
//紅黑樹的節點顏色--黑色
private static final boolean BLACK = true;
// 靜態內部類用來表示節點型別
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; //
}
}
類構造方法
public TreeMap() { // 1,無參構造方法
comparator = null; // 預設比較機制
}
public TreeMap(Comparator<? super K> comparator) { // 2,自定義比較器的構造方法
this.comparator = comparator;
}
public TreeMap(Map<? extends K, ? extends V> m) { // 3,構造已知Map物件為TreeMap
comparator = null; // 預設比較機制
putAll(m);
}
public TreeMap(SortedMap<K, ? extends V> m) { // 4,構造已知的SortedMap物件為TreeMap
comparator = m.comparator(); // 使用已知物件的構造器
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
TreeMap put()方法實現分析
在TreeMap的put()的實現方法中主要分為兩個步驟,第一:構建排序二叉樹,第二:平衡二叉樹。
對於排序二叉樹的建立,其新增節點的過程如下:
- 以根節點為初始節點進行檢索。
- 與當前節點進行比對,若新增節點值較大,則以當前節點的右子節點作為新的當前節點。否則以當前節點的左子節點作為新的當前節點。
- 迴圈遞迴2步驟知道檢索出合適的葉子節點為止。
- 將新增節點與3步驟中找到的節點進行比對,如果新增節點較大,則新增為右子節點;否則新增為左子節點。
public V put(K key, V value) {
//用t表示二叉樹的當前節點
Entry<K,V> t = root;
//t為null表示一個空樹,即TreeMap中沒有任何元素,直接插入
if (t == null) {
//比較key值,空樹還需要比較、排序?
compare(key, key); // type (and possibly null) check
//將新的key-value鍵值對建立為一個Entry節點,並將該節點賦予給root
root = new Entry<>(key, value, null);
//容器的size = 1,表示TreeMap集合中存在一個元素
size = 1;
//修改次數 + 1
modCount++;
return null;
}
int cmp; //cmp表示key排序的返回結果
Entry<K,V> parent; //父節點
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; //指定的排序演算法
//如果cpr不為空,則採用既定的排序演算法進行建立TreeMap集合
if (cpr != null) {
do {
parent = t; //parent指向上次迴圈後的t
//比較新增節點的key和當前節點key的大小
cmp = cpr.compare(key, t.key);
//cmp返回值小於0,表示新增節點的key小於當前節點的key,則以當前節點的左子節點作為新的當前節點
if (cmp < 0)
t = t.left;
//cmp返回值大於0,表示新增節點的key大於當前節點的key,則以當前節點的右子節點作為新的當前節點
else if (cmp > 0)
t = t.right;
//cmp返回值等於0,表示兩個key值相等,則新值覆蓋舊值,並返回新值
else
return t.setValue(value);
} while (t != null);
}
//如果cpr為空,則採用預設的排序演算法進行建立TreeMap集合
else {
if (key == null) //key值為空丟擲異常
throw new NullPointerException();
/* 下面處理過程和上面一樣 */
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);
}
//將新增節點當做parent的子節點
Entry<K,V> e = new Entry<>(key, value, parent);
//如果新增節點的key小於parent的key,則當做左子節點
if (cmp < 0)
parent.left = e;
//如果新增節點的key大於parent的key,則當做右子節點
else
parent.right = e;
/*
* 上面已經完成了排序二叉樹的的構建,將新增節點插入該樹中的合適位置
* 下面fixAfterInsertion()方法就是對這棵樹進行調整、平衡,具體過程參考上面的五種情況
*/
fixAfterInsertion(e);
//TreeMap元素數量 + 1
size++;
//TreeMap容器修改次數 + 1
modCount++;
return null;
}
上面程式碼中do{}程式碼塊是實現排序二叉樹的核心演算法,通過該演算法我們可以確認新增節點在該樹的正確位置。找到正確位置後將插入即可,這樣做了其實還沒有完成,因為我知道TreeMap的底層實現是紅黑樹,紅黑樹是一棵平衡排序二叉樹,普通的排序二叉樹可能會出現失衡的情況,所以下一步就是要進行調整。fixAfterInsertion(e); 調整的過程務必會涉及到紅黑樹的左旋、右旋、著色三個基本操作。程式碼如下:
/**
* 新增節點後的修復操作
* x 表示新增節點
*/
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED; //新增節點的顏色為紅色
//迴圈 直到 x不是根節點,且x的父節點不為紅色
while (x != null && x != root && x.parent.color == RED) {
//如果X的父節點(P)是其父節點的父節點(G)的左節點
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//獲取X的叔節點(U)
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
//如果X的叔節點(U) 為紅色(情況三)
if (colorOf(y) == RED) {
//將X的父節點(P)設定為黑色
setColor(parentOf(x), BLACK);
//將X的叔節點(U)設定為黑色
setColor(y, BLACK);
//將X的父節點的父節點(G)設定紅色
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
}
//如果X的叔節點(U為黑色);這裡會存在兩種情況(情況四、情況五)
else {
//如果X節點為其父節點(P)的右子樹,則進行左旋轉(情況四)
if (x == rightOf(parentOf(x))) {
//將X的父節點作為X
x = parentOf(x);
//右旋轉
rotateLeft(x);
}
//(情況五)
//將X的父節點(P)設定為黑色
setColor(parentOf(x), BLACK);
//將X的父節點的父節點(G)設定紅色
setColor(parentOf(parentOf(x)), RED);
//以X的父節點的父節點(G)為中心右旋轉
rotateRight(parentOf(parentOf(x)));
}
}
//如果X的父節點(P)是其父節點的父節點(G)的右節點
else {
//獲取X的叔節點(U)
Entry<K,V> y = leftOf(parentOf(parentOf(x)));
//如果X的叔節點(U) 為紅色(情況三)
if (colorOf(y) == RED) {
//將X的父節點(P)設定為黑色
setColor(parentOf(x), BLACK);
//將X的叔節點(U)設定為黑色
setColor(y, BLACK);
//將X的父節點的父節點(G)設定紅色
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
}
//如果X的叔節點(U為黑色);這裡會存在兩種情況(情況四、情況五)
else {
//如果X節點為其父節點(P)的右子樹,則進行左旋轉(情況四)
if (x == leftOf(parentOf(x))) {
//將X的父節點作為X
x = parentOf(x);
//右旋轉
rotateRight(x);
}
//(情況五)
//將X的父節點(P)設定為黑色
setColor(parentOf(x), BLACK);
//將X的父節點的父節點(G)設定紅色
setColor(parentOf(parentOf(x)), RED);
//以X的父節點的父節點(G)為中心右旋轉
rotateLeft(parentOf(parentOf(x)));
}
}
}
//將根節點G強制設定為黑色
root.color = BLACK;
}
TreeMap delete()方法:針對於紅黑樹的增加節點而言,刪除顯得更加複雜,使原本就複雜的紅黑樹變得更加複雜。同時刪除節點和增加節點一樣,同樣是找到刪除的節點,刪除之後調整紅黑樹。但是這裡的刪除節點並不是直接刪除,而是通過走了“彎路”通過一種捷徑來刪除的:找到被刪除的節點D的子節點C,用C來替代D,不是直接刪除D,因為D被C替代了,直接刪除C即可。所以這裡就將刪除父節點D的事情轉變為了刪除子節點C的事情,這樣處理就將複雜的刪除事件簡單化了。子節點C的規則是:右分支最左邊,或者 左分支最右邊的。
一幫以getEntry()方法為基礎的獲取元素的方法,其中包括containsKey(),get(),remove()等。
final Entry<K,V> getEntry(Object key) {
// 如果有自定義比較器物件,就按照自定義規則遍歷二叉樹
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;
}
一幫以getFirstEntry(),getLastEntry()為基礎的獲取頭和尾元素的方法,其中包括:firstKey(),lastKey();firstEntry(),lastEntry();pollFirstEntry(),pollLastEntry()
final Entry<K,V> getFirstEntry() { // 獲取第一個元素也就是最小的元素,一直遍歷左子樹
Entry<K,V> p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
final Entry<K,V> getLastEntry() { // 獲取最後個元素也就是最大的元素,一直遍歷右子樹
Entry<K,V> p = root;
if (p != null)
while (p.right != null)
p = p.right;
return p;
}