TreeMap和TreeSet基于红黑树,天然排序但不接受null;键/元素须可比较(Comparable或Comparator),否则抛ClassCastException或NullPointerException;去重依赖compareTo而非equals,需保持语义一致;subMap等为原Map视图,修改互通;不维护插入顺序,仅保证排序顺序;操作时间复杂度均为O(log n)。

TreeMap 和 TreeSet 底层都基于红黑树,天然支持排序,但它们不接受 null 键或元素(否则抛 NullPointerException),且排序依赖 Comparable 实现或外部 Comparator —— 这是使用前必须确认的底线。
TreeMap 的键必须可比较,否则运行时报错
TreeMap 要求所有键能比较大小。如果键类型没实现 Comparable,又没传 Comparator,第一次 put() 就会抛 ClassCastException。
- 常见错误:用自定义类(如
Person)作键,但没重写compareTo(),也没传Comparator - 正确做法:要么让类实现
Comparable,要么构造时显式传Comparator - 注意:即使键是
String或Integer这类自带Comparable的类型,若插入null键,仍会立即报NullPointerException
TreeMapmap = new TreeMap<>(); map.put("a", 1); // OK map.put(null, 2); // 抛 NullPointerException
TreeSet 对元素去重靠 compareTo() / compare(),不是 equals()
TreeSet 判断“重复”不调用 equals(),而是看 compareTo() 是否返回 0。这意味着:如果 compareTo() 和 equals() 语义不一致,会出现逻辑矛盾。
- 例如:两个
Person对象name相同但id不同,若compareTo()只比name,则它们会被视为“相同”,后者无法加入 TreeSet - 反之,若
compareTo()返回 0 但equals()返回false,集合行为将违反 Set 合约(比如contains()可能返回true,但遍历时找不到该对象) - 建议:让
compareTo()和equals()基于同一组字段,保持一致
TreeMap 的 subMap/headMap/tailMap 是视图,修改影响原 Map
这些方法返回的是原 TreeMap 的“子视图”,不是副本。对子视图的增删改,会实时反映到原 Map 中,反之亦然。
立即学习“Java免费学习笔记(深入)”;
-
subMap(fromKey, toKey)是左闭右开区间;fromKey必须 ≤toKey,否则抛IllegalArgumentException - 若原 Map 中不存在
fromKey,headMap(toKey)仍正常工作,它只关心键的顺序关系 - 性能无额外拷贝开销,但要注意并发场景下可能因共享结构引发意外修改
TreeMapm = new TreeMap<>(); m.put(1, "a"); m.put(3, "c"); m.put(5, "e"); SortedMap sub = m.subMap(2, 4); // 包含键 3 sub.put(3, "C"); // m 中键 3 的值也变成 "C"
TreeSet 无法保证插入顺序,只保证排序后顺序
和 LinkedHashSet 不同,TreeSet 没有记录插入时间的能力。哪怕你按 5、3、7 的顺序添加,迭代结果永远是 3、5、7 —— 它只维护红黑树结构,不保留任何插入痕迹。
- 如果需要“按插入顺序 + 去重”,得用
LinkedHashSet;如果需要“按某种业务规则排序 + 去重”,才选TreeSet - 注意:TreeSet 构造时传入
Comparator,其排序逻辑会完全覆盖自然顺序,且该逻辑必须满足“自反性、传递性、对称性”,否则行为不可预测 - 一个易忽略点:TreeSet 的
iterator()返回的是升序迭代器;若要降序,得用descendingIterator(),而不是靠反转集合
红黑树的平衡机制决定了 TreeMap/TreeSet 的 get()、add()、remove() 都是 O(log n);但如果你误以为它们能替代 ArrayList 做随机索引访问(比如想取“第 3 个元素”),就会卡在没有 get(int index) 方法上 —— 这事得靠 Stream 或手动遍历,别硬套数组思维。










