【本文是爲了梳理知識的總結性文章,總結了一些自認爲相關的重要知識點,只爲鞏固記憶以及技術交流,忘批評指正。其中參考了很多前輩的文章,包括圖片也是引用,如有冒犯,侵刪。】
0 存儲結構
TreeMap 是一個有序的Map,內部按照Key的排序結果來組織。一般如果沒有需要排序的情況下,我們都使用HashMap或者多線程下使用ConcurrentHashMap,因爲TreeMap的插入和刪除的效率沒有前兩者高。但是如果需要有序的Map,那麼就只能選TreeMap了,HashMap是基於Hash散列的,因此是無序的。從底層實現來看,TreeMap 是基於紅黑樹實現的,在學習之前需要先了解紅黑樹的基礎知識,可以參考數據結構之紅黑樹Java實現。
1 類定義
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
2 紅黑樹節點Entry結構
// 使用boolean值表示紅黑樹節點顏色
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; // 節點顏色
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
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;
return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
}
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;
}
}
3 屬性
/**
* 用於維護此TreeMap中順序的比較器,如果它爲null,則使用key的自然順序。
*/
private final Comparator<? super K> comparator;
// 樹的根節點
private transient Entry<K,V> root;
/**
* 樹中節點的個數
*/
private transient int size = 0;
/**
* 樹進行結構性修改的次數
*/
private transient int modCount = 0;
4 構造函數
主要用於初始化 comparator
// 默認使用自然排序,插入TreeMap的Key必須實現Comparable接口才能進行比較
public TreeMap() {
comparator = null;
}
// 使用指定的比較器,
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
// 基於現有的Map構造TreeMap
public TreeMap(Map<? extends K, ? extends V> m) {
comparator = null;
putAll(m);
}
// 構造一個和SortedMap具有相同順序和元素的新Map
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) {
}
}
5 常用方法
get(Object key)
public V get(Object key) {
// 調用getEntry方法進行查找
Entry<K,V> p = getEntry(key);
// 不存在返回null
return (p==null ? null : p.value);
}
getEntry方法,無論是基於自定義比較器的查找,還是基於自然排序比較器的比較,都是在二叉樹下的查找。當前元素大於目標元素,向左找;當前元素小於目標元素,向右找。
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;
}
put(K key,V value)
插入時先進行查找,看是否存在相同key的節點,存在就直接替換value,不存的話能找到要插入的位置parent, 構造新節點進行插入,然後調用fixAfterInsertion方法進行插入修復,插入修復設計紅黑樹的重新着色和旋轉,詳情見數據結構之紅黑樹Java實現。
public V put(K key, V value) {
// 根節點爲空的情況
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // 類型檢查,有可能爲null
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,直接覆蓋,返回舊值
} 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); // 存在重複的key,直接覆蓋,返回舊值
} 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;
}
remove(Object key)
public V remove(Object key) {
// 先找到目標節點
Entry<K,V> p = getEntry(key);
if (p == null)
return null;
V oldValue = p.value;
// 調用deleteEntry進行刪除
deleteEntry(p);
return oldValue;
}
deleteEntry刪除方法, 刪除過程,可以簡單分爲4種情況:
- 只有一個節點的情況;
- 刪除點p的左右子樹都爲空;
- 只有一棵子樹非空;
- 刪除點p的左右子樹都非空。
對於情況1,直接將根節點root置爲null就可以;
對於情況2,直接將p刪除,將p的parent指向p的指針置爲null;
對於情況3,用非空子樹替代p;
對於情況4,可以用p的後繼s(樹中大於p的最小的那個元素)代替p,然後刪除後繼s。後繼s一定不是左右子樹非空,就可以使用情況1、2、3進行處理。
刪除修復方法fixAfterDeletion詳見,數據結構之紅黑樹Java實現。
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.
// 刪除節點p的左右子樹都非空,將P 指向後繼元素
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.
// 刪除點p只有一棵子樹非空,從子樹中選出替代元素
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
// 替換parent指向
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
// 刪除P節點
p.left = p.right = p.parent = null;
// Fix replacement
// 插入修復
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // 只有一個節點p的情況
root = null;
} else { // 刪除點p的左右子樹都爲空
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;
}
}
}
successor(Entry<K,V> t)
尋找後繼的方法,對於一棵二叉查找樹,給定節點t,其後繼(樹中比大於t的最小的那個元素)可以通過如下方式找到:
- t 的右子樹不空,則t的後繼是其右子樹中最小的那個元素。
- t 的右孩子爲空,則t的後繼是其第一個向左走的祖先。
// 尋找節點後繼函數successor()
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
else if (t.right != null) {// t的右子樹不空,則t的後繼是其右子樹中最小的那個元素
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {// t的右孩子爲空,則t的後繼是其第一個向左走的祖先
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
firstEntry()
尋找第一個節點,根據二叉查找樹的性質,樹的最做左邊的節點最小。
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;
}
// 返回簡單不可變entry,如果爲null,則返回null
static <K,V> Map.Entry<K,V> exportEntry(TreeMap.Entry<K,V> e) {
return (e == null) ? null :
new AbstractMap.SimpleImmutableEntry<>(e);
}
lastEntry()
返回樹的最大的元素,根據二叉查找樹的性質,樹的最做右邊的節點最小。
public Map.Entry<K,V> lastEntry() {
return exportEntry(getLastEntry());
}
// 找到根節點的最右邊節點
final Entry<K,V> getLastEntry() {
Entry<K,V> p = root;
if (p != null)
while (p.right != null)
p = p.right;
return p;
}
lowerEntry(K key)
找到剛好小於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);
if (cmp > 0) {
// key 比當前p.key大
if (p.right != null)
p = p.right; // 向右查找
else
// 比最大的右加點還大,返回最大值
return p;
} else {
// key 比當前p.key大
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;
}
}
}
return null;
}
higherEntry(K key)
public Map.Entry<K,V> higherEntry(K key) {
return exportEntry(getHigherEntry(key));
}
final Entry<K,V> getHigherEntry(K key) {
Entry<K,V> p = root;
while (p != null) {
int cmp = compare(key, p.key);
if (cmp < 0) {
// key 比當前p.key小
if (p.left != null)
p = p.left; // 向左查找
else
return p; // 比最小的左加點還小,返回最小值
} else {
// key 比當前p.key大
if (p.right != null) {
p = p.right; // 向右查找
} else {
// 比最低的右節點大,向上查找第一個右拐的父節點
Entry<K,V> parent = p.parent;
Entry<K,V> ch = p;
while (parent != null && ch == parent.right) {
ch = parent;
parent = parent.parent;
}
return parent;
}
}
}
return null;
}
......