在Java中如何使用TreeMap与TreeSet_Java集合排序与树结构说明

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
  • 注意:即使键是 StringInteger 这类自带 Comparable 的类型,若插入 null 键,仍会立即报 NullPointerException
TreeMap map = 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 中,反之亦然。

  • subMap(fromKey, toKey) 是左闭右开区间;fromKey 必须 ≤ toKey,否则抛 IllegalArgumentException
  • 若原 Map 中不存在 fromKeyheadMap(toKey) 仍正常工作,它只关心键的顺序关系
  • 性能无额外拷贝开销,但要注意并发场景下可能因共享结构引发意外修改
TreeMap m = 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 或手动遍历,别硬套数组思维。