在 Java 集合框架中,TreeSet 是唯一實現 SortedSet 介面的類別,它能確保元素始終處於排序狀態。本文將從 Set 介面特性切入,對比 HashSet,深入剖析 TreeSet 的自然排序與定制排序、底層紅黑樹結構、唯一性判斷邏輯,以及原始碼層面的關鍵方法。
add() 若元素已存在回傳 false。equals() 而非 ==。兩物件 equals() 回傳 true,Set 視為相同。HashMap(陣列+鏈表/紅黑樹)。hashCode() 相同 且 equals() 相同。equals() 必須覆寫 hashCode(),且用於 equals 比較的欄位必須參與 hashCode 計算。| 排序方式 | 實現介面 | 適用場景 | 優先級 |
|---|---|---|---|
| 自然排序 | 元素實作 Comparable<E>,覆寫 compareTo(E o) | 元素類別由自己控制,且有「自然」大小順序(如 Integer、String、日期) | 低 |
| 定制排序 | 建構子傳入 Comparator<E>,覆寫 compare(E o1, E o2) | 元素類別無法修改、或需多種排序規則、或不想汙染領域模型 | 高(構造子指定時覆蓋自然排序) |
compareTo 回傳 負數:this < o;0:相等;正數:this > o。equals() 恆回傳 true,則 compareTo() 應回傳 0(強一致性建議,非強制)。compareTo 內反轉邏輯或用 Comparator.reverseOrder()。
// 匿名內部類寫法
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getAge() - s2.getAge(); // 按年齡升序
}
});
// Java 8+ Lambda 簡寫
TreeSet<Student> ts2 = new TreeSet<>(Comparator.comparingInt(Student::getAge));
這與 HashSet 的 equals() 判斷不同。注意:若排序規則導致不同業務物件比較結果為 0,TreeSet 會拒絕後加入的物件,造成資料遺失!務必確保比較器能區分不同業務實體(如加上 ID 做次級排序)。
TreeSet 內部持有一個 NavigableMap<E, Object> 實例變數,實際類別為 TreeMap。所有操作委派給該 Map:
add(e) → map.put(e, PRESENT)contains(e) → map.containsKey(e)map.navigableKeySet().iterator()TreeMap 採用紅黑樹(自平衡二元搜尋樹),保證增刪查 O(log n)。原始碼關鍵點:
put():從根節點開始比較,決定往左/右子樹,找到插入點建立 Entry 節點,最後 fixAfterInsertion() 旋轉著色維持紅黑樹性質。getEntry():自然排序用 Comparable,定制排序用 Comparator,同樣從根節點二分查找。
中序遍歷(左-根-右)即可得到升序序列。
compareTo 與 equals 不一致:TreeSet 視為不同元素,但 HashSet 視為相同。解決:保持強一致性。Collections.synchronizedSortedSet 或並發包 ConcurrentSkipListSet。TreeSet 以紅黑樹賦予有序性,適合「需要排序、範圍查詢(subSet/headSet/tailSet)、且資料量中等」的場景。掌握其兩種排序機制與唯一性判斷邏輯,能避開大多數生產環境 Bug。