TreeMap底層是紅黑樹,在java8 HashMap也引入了紅黑樹,那麼什麼是紅黑樹?紅黑樹是一種二叉搜索樹,它在每一個結點上增長了一個存儲位來表示結點的顏色,能夠是RED或BLACK。經過對任何一條從根到葉子的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其餘路徑長出2倍,於是是近似於平衡的。(出自算法導論)html
既然紅黑樹是一種二叉搜索樹,那麼咱們先來了解其性質:
①.左子樹上全部結點的值小於或等於其根結點的值
②.右子樹上全部結點的值大於或等於其根結點的值
③.任意節點的左、右子樹也分別爲二叉搜索樹
以下:
java
紅黑樹是每一個節點都帶有顏色屬性的二叉搜索樹,顏色或紅色或黑色。除了符合二叉搜索的基本特性外,它還附加以下性質:
①.節點是紅色或黑色
②.根節點是黑色
③.每一個葉節點(NIL節點,空節點)是黑色的
④.每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)
⑤.從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點
一顆典型的紅黑樹:
node
左旋:算法
右旋:安全
言歸正傳回到treeMap,咱們先看下它的繼承關係圖:
數據結構
// 比較器用於排序,若爲null使用天然排序維持key順序
private final Comparator comparator;
// 根節點
private transient Entry root;
// 節點數
private transient int size = 0;
// 修改次數,fail-fast
private transient int modCount = 0;
//節點顏色
private static final boolean RED = false;
private static final boolean BLACK = true;
/**
* 節點
*/
static final class Entry implements Map.Entry {
K key; //鍵
V value; //值
Entry left; //左子樹
Entry right; //右子樹
Entry parent; //父親
boolean color = BLACK; //顏色
Entry(K key, V value, Entry parent) {...}
public K getKey() {...}
public V getValue() {...}
public V setValue(V value) {...}
public boolean equals(Object o) {...}
public int hashCode() {...}
public String toString() {...}
}
複製代碼
/**
* 無參構造,天然排序(從小到大)。要求key實現Comparable接口,會調用key重寫的compareTo方法進行比較
* 若key沒有實現comparable接口,運行時報錯(java.lang.ClassCastException)
*/
public TreeMap() {
comparator = null;
}
/**
* 指定比較器,若不爲null會調用其compare方法進行比較,無需鍵實現comparable接口
*/
public TreeMap(Comparator comparator) {
this.comparator = comparator;
}
/**
* 將map轉爲treeMap,比較器爲null,注意key
*/
public TreeMap(Map m) {
comparator = null;
putAll(m);
}
/**
* 將map轉爲treeMap,比較器爲SortMap中的comparator
*/
public TreeMap(SortedMap m) {
comparator = m.comparator();
try {
buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
} catch (java.io.IOException cannotHappen) {
} catch (ClassNotFoundException cannotHappen) {
}
}
複製代碼
public V put(K key, V value) {
// 獲取根節點
Entry t = root;
// 若TreeMap爲空則直接插入
if (t == null) {
//校驗:若比較器爲null則key必須實現Comparable接口,若不爲null,key可爲null
compare(key, key); // type (and possibly null) check
//設爲頭節點
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
// 記錄key排序比較結果
int cmp;
// 記錄父節點
Entry parent;
// split comparator and comparable paths
Comparator cpr = comparator;
// 若存在比較器,循環查找位置cmp小於0往左找,大於0往右找,直至等於0進行替換
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);
}
// 若不存在比較器,key必須實現Comparable接口
else {
//null沒法實現Comparable接口沒有compareTo方法故拋異常
if (key == null)
throw new NullPointerException();
//獲取比較器,處理方式與上面一致
@SuppressWarnings("unchecked")
Comparable k = (Comparable) 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);
}
//若當前TreeMap中沒有此key則新建結點,不管上述哪一個分支成立parent必定指向當前某個葉子結點
Entry e = new Entry<>(key, value, parent);
//小於0則爲左子樹
if (cmp < 0)
parent.left = e;
//大於0則爲右子樹
else
parent.right = e;
//保證紅黑樹性質
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
複製代碼
get方法與put思路大體相同app
public V get(Object key) {
Entry p = getEntry(key);
//找到對應節點返回其值,沒有找到返回null
return (p==null ? null : p.value);
}
final Entry getEntry(Object key) {
// Offload comparator-based version for sake of performance
// 若比較器不爲null
if (comparator != null)
return getEntryUsingComparator(key);
// 若比較器爲null,則key必須實現Comparable接口,null不能拋異常
if (key == null)
throw new NullPointerException();
//用key的compareTo方法,從根節點尋找,若沒有找返回null
@SuppressWarnings("unchecked")
Comparable k = (Comparable) key;
Entry 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 getEntryUsingComparator(Object key) {
@SuppressWarnings("unchecked")
K k = (K) key;
Comparator cpr = comparator;
//處理方式一致
if (cpr != null) {
Entry 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;
}
複製代碼
採用相似中序遍歷(LDR左根右)方式來遍歷整個紅黑樹找到相應valuepost
public boolean containsValue(Object value) {
for (Entry e = getFirstEntry(); e != null; e = successor(e))
if (valEquals(value, e.value))
return true;
return false;
}
/**
* 返回最小節點
*/
final Entry getFirstEntry() {
Entry p = root;
if (p != null)
while (p.left != null)
p = p.left;
return p;
}
/**
* 找後繼節點
*/
static TreeMap.Entry successor(Entry t) {
if (t == null)
return null;
//若存在右子樹,則返回右子樹中最小節點
else if (t.right != null) {
Entry p = t.right;
while (p.left != null)
p = p.left;
return p;
//若不存在,從當前節點往上找,若其父節點不爲null且它是父節點的右子樹則繼續找父節點
//直至條件不成立,返回父節點
} else {
Entry p = t.parent;
Entry ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
複製代碼
successor方法找節點的後繼節點:
①.若節點爲空沒有後繼
②.若節點有右子樹,後繼爲右子樹的最左節點
③.若節點沒有右子樹,後繼爲該節點所在左子樹的第一個祖先節點
ui
第一個無需多言,第二個也容易,看圖p的後繼節點s:
this
public V remove(Object key) {
//獲取key所對應的節點
Entry p = getEntry(key);
//若節點爲空返回null
if (p == null)
return null;
//若不爲null,刪除節點返回其值
V oldValue = p.value;
deleteEntry(p);
return oldValue;
}
private void deleteEntry(Entry p) {
modCount++;
size--;
//若p左子樹和右子樹都不爲null,將p的key與value替換成後繼的,將p指向後繼
if (p.left != null && p.right != null) {
Entry s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// replacement爲替代節點
Entry replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
replacement.parent = p.parent;
//若p沒有父節點,則根節點設爲replacement
if (p.parent == null)
root = replacement;
//若p爲左節點,則用replacement替換左節點
else if (p == p.parent.left)
p.parent.left = replacement;
//若p爲右節點,則用replacement替換右節點
else
p.parent.right = replacement;
//刪除p節點
p.left = p.right = p.parent = null;
// 若p爲黑色則須要調整
if (p.color == BLACK)
fixAfterDeletion(replacement);
//若p沒有父節點即p爲根節點,根節點置空
} else if (p.parent == null) { // return if we are the only node.
root = null;
//p沒有子節點
} else { // No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
fixAfterDeletion(p);
//刪除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;
}
}
}
複製代碼
分三種狀況:
①.葉子結點:直接將其父節點對應孩子置空,若刪除左葉子結點則將其父結點左子樹置空,若刪除右葉子結點則將其父結點右子樹置空
刪除節點A:
刪除節點G:
③.兩個孩子:先找到後繼,找到後,替換當前節點的內容爲後繼節點,而後再刪除後繼節點,由於這個後繼節點必定沒有左孩子,因此就將兩個孩子的狀況轉換爲了前面兩種狀況
刪除節點B:
①.TreeMap底層是紅黑樹,集合有序線程不安全。
②.若比較器爲空則key必定不能爲null,若比較器不爲空則key能夠爲null由TreeMap其比較器而定
③.containsValue方法採用中序遍歷(LDR左根右)方式遍歷整個TreeMap
在上一篇文章(java8HashMap)寫了鏈表與紅黑樹互轉,本文略微說起紅黑樹相關知識主要圍繞源碼講述TreeMap的一些方法,下篇主要以TreeMap插入刪除後如何維持其特性。
https://juejin.im/post/5828ef582f301e0058586fde http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html https://blog.csdn.net/v_july_v/article/details/6105630