treeset通过navigableset接口支持o(log n)范围查询,利用红黑树直接切子树;subset、headset、tailset返回视图而非副本;higher()/lower()查严格邻值,ceiling()/floor()查含等邻值;声明用navigableset更灵活安全。

TreeSet怎么支持按范围查元素
TreeSet 实现了 NavigableSet,所以天然支持带边界的范围视图,不是靠遍历过滤,而是利用红黑树结构直接切出子树,时间复杂度 O(log n) 起步,查 [from, to) 或 (from, to] 都行。
关键在四个方法:subSet()、headSet()、tailSet(),它们都带两个重载:一个只传 toElement(默认包含下界、不包含上界),另一个多加两个 boolean 参数控制开闭区间。
-
subSet(fromElement, fromInclusive, toElement, toInclusive)—— 最灵活,但注意fromElement必须 ≤toElement,否则抛IllegalArgumentException - 返回的仍是
NavigableSet,修改它会同步反映到原TreeSet(是视图,非副本) - 如果
fromElement不存在,且fromInclusive = true,会从第一个 ≥ 它的元素开始;fromInclusive = false则从第一个 > 它的元素开始
怎么找“比x大一点”的那个元素
用 higher() 和 lower(),不是 ceiling() 和 floor()——这是最容易混的点。区别就一句话:higher(x) 找严格大于 x 的最小元素;ceiling(x) 找 ≥ x 的最小元素(x 存在就返回 x)。
常见误用场景:想跳过当前值查下一个,结果用了 ceiling(),白忙活。
- 查“下一个”用
higher(x);查“上一个”用lower(x) - 查“不小于x的第一个”用
ceiling(x);查“不大于x的最后一个”用floor(x) - 所有这四个方法查不到时都返回
null(非空集合中),别直接解引用 - 如果集合是
Integer等可空类型,null返回值和元素值本身要区分开(比如存了null元素?不行,TreeSet不允许null)
为什么用NavigableSet声明变量而不是TreeSet
因为 NavigableSet 是接口,暴露了全部范围与临近查询方法;而直接用 TreeSet 变量,虽然实例是它,但编译期看不到 higher() 等方法(除非强转)。
更关键的是,这样写能屏蔽实现细节——万一哪天换成 ConcurrentSkipListSet(它也实现 NavigableSet,且线程安全),代码完全不用改。
- 声明类型优先用
NavigableSet<string></string>,而非TreeSet<string></string> - 构造时当然还是
new TreeSet()或new ConcurrentSkipListSet() - 如果用
TreeSet声明,调subSet(..., true, ..., true)会编译报错:该重载只在NavigableSet中定义
遇到ClassCastException或NullPointerException怎么办
这两个异常基本都卡在比较逻辑上:TreeSet 内部依赖元素的自然序(Comparable)或外部 Comparator,一旦比较过程崩了,整个操作就挂。
典型坑:元素类没实现 Comparable,又没传 Comparator;或者 Comparator 里写了 a.field - b.field 导致整数溢出;或者比较时访问了 null 字段。
- 确保所有插入元素都满足比较契约:自反、对称、传递;尤其注意
null值处理(Comparator.nullsFirst()可封装) - 避免在
Comparator中做耗时或可能抛异常的操作(如 IO、数据库调用) - 调试时打印
treeSet.comparator()看是否为null,再确认元素类型是否真实现了Comparable - 如果元素是自定义类,检查
compareTo()是否把null当合法参数处理(标准写法应先判空)
事情说清了就结束。真正难的不是记住方法名,是理解「视图」不是拷贝、「更高」不等于「不小于」、以及比较器崩了整个集合就废——这些地方一错,问题往往藏得深。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











