TreeSet 與 TreeMap 是 Java 集合框架中有序容器的核心實現。本文揭示兩者的包裝關係,深入剖析 TreeMap.put() 插入邏輯、getEntry() 查找邏輯,並以圖解方式演示紅黑樹的中序遍歷如何產生有序序列。
TreeSet 內部持有一個 NavigableMap<E, Object> 變數 map,實際類別為 TreeMap。所有操作委派給該 Map:
// TreeSet 關鍵片段
private transient NavigableMap<E, Object> map;
private static final Object PRESENT = new Object();
public TreeSet() {
this.map = new TreeMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
因此,掌握 TreeMap 即掌握 TreeSet。
核心邏輯:從根節點開始,根據比較器(自然排序或定制排序)決定往左/右子樹,找到插入點建立 Entry 節點,最後修復紅黑樹性質。
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) { // 空樹:建立根節點
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
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);
} while (t != null);
}
// 走到這裡說明 t==null,parent 是插入點的父節點
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;
}
關鍵點:
cpr.compare,為空用 Comparable.compareTo。fixAfterInsertion 維持紅黑樹 5 大性質。
// 自然排序查找
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;
}
// 定制排序查找
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;
}
查找同樣是二分搜尋,時間複雜度 O(log n)。
TreeMap 實作 NavigableMap,其 keySet()、values()、entrySet() 迭代器皆按中序遍歷(左-根-右)輸出,保證升序。
遍歷邏輯(簡化):

圖中數字標示遍歷順序:1→2→3→4→5→6→7,對應鍵值由小到大。
| 特性 | 自然排序 | 定制排序 |
|---|---|---|
| 實現介面 | 元素實作 Comparable | 建構子傳入 Comparator |
| 比較方法 | compareTo(E o) | compare(E o1, E o2) |
| 優先級 | 低 | 高(覆蓋自然排序) |
| 適用場景 | 類別有自然順序 | 無法修改類別、多種排序、解耦 |
compare 回傳 0 但 equals 回傳 false,導致 TreeSet 視為重複而 HashSet 視為不同。ConcurrentSkipListMap/Set(跳表實現,同樣有序)。TreeMap 以紅黑樹為骨架,TreeSet 輕量包裝,兩者共享同一套插入、查找、遍歷邏輯。讀懂 put()、getEntry()、fixAfterInsertion() 三大核心,即可洞悉有序容器的本質。