NavigableSet 的 ceiling() 和 floor() 不总返回预期元素,因其依赖排序逻辑与自然顺序(或 Comparator),null 或未实现 Comparable 的类型会抛异常;ceiling(e) 找 ≥ e 的最小元素,floor(e) 找 ≤ e 的最大元素,均找不到时返回 null。

为什么 NavigableSet 的 ceiling() 和 floor() 不总返回预期元素?
因为它们依赖底层实现的排序逻辑和元素的自然顺序(或自定义 Comparator),且对 null 值、未实现 Comparable 的类型直接抛异常。比如用 TreeSet 存 String 时传入 "abc",ceiling("ab") 返回 "abc";但若存的是自定义对象却没重写 compareTo(),运行时就崩在 ClassCastException。
-
ceiling(e)找 ≥e的最小元素,找不到返回null -
floor(e)找 ≤e的最大元素,找不到也返回null - 如果集合用
Comparator.nullsFirst()构建,null可能被当作有效元素参与比较——但多数 JDK 版本仍禁止向NavigableSet插入null - 注意:这些方法不保证 O(1),
TreeSet实现下是 O(log n),而ConcurrentSkipListSet也是对数级,别误以为是哈希查找
headSet()、tailSet()、subSet() 的边界包含规则怎么记?
全看第三个布尔参数(JDK 6+):true 表示包含边界,false 表示不包含。老代码里只传两个参数的版本默认“不包含上界”,容易踩坑。
-
headSet(toElement, true)→ 小于等于toElement的所有元素 -
tailSet(fromElement, false)→ 大于fromElement的所有元素(不含等值) -
subSet(from, true, to, false)→ ≥from且 <to - 返回的子集仍是
NavigableSet,但它是原集合的**视图**:改它 = 改原集合,反之亦然 - 如果传入的边界值本身不在原集中,不影响视图范围——只要排序位置合法就行
用 ConcurrentSkipListSet 替代 TreeSet 时,lower() 和 higher() 有什么不同?
语义一样:lower(e) 是严格小于 e 的最大元素,higher(e) 是严格大于 e 的最小元素。但并发安全不是白来的——它牺牲了部分实时性。
-
ConcurrentSkipListSet不加锁,靠跳表结构实现无锁并发,所以lower()/higher()看到的可能是“某个时间点的快照”,而非强一致状态 - 和
TreeSet不同,它不支持传入Comparator构造时用null元素(会 NPE),哪怕你写了Comparator.nullsLast() - 性能上,平均查找是 O(log n),但常数比
TreeSet大;如果只是单线程,别为了“听起来高级”硬切 - 注意:它的
size()是 O(n),别在循环里反复调用
什么时候该坚持用 TreeSet,而不是泛化成 NavigableSet?
当你需要确保底层是红黑树、且要调用 TreeSet 特有方法时,比如 pollFirst() 或 descendingIterator()——这些在接口里没有,但 NavigableSet 已经覆盖绝大多数导航需求。
立即学习“Java免费学习笔记(深入)”;
- 声明变量优先用
NavigableSet<String> set = new TreeSet<>();,利于替换实现 - 但如果业务逻辑强依赖
TreeSet的comparator()返回null表示自然序(而NavigableSet.comparator()只保证返回当前比较器),就得留心 - 序列化时,
TreeSet写出的是具体类名,反序列化必须对应;用接口声明但存ConcurrentSkipListSet实例,序列化可能失败 - 最易忽略的一点:
NavigableSet不继承Cloneable,TreeSet却实现了clone()——需要浅拷贝时,别指望接口变量能直接.clone()










