TreeSet的subSet查不到预期元素,根本原因是默认“前闭后开”区间(如subSet(3,7)返回[3,7)不包含7),需显式指定包含性;自定义Comparator时须确保比较逻辑与范围语义一致,且参数必须可比。

TreeSet的subSet为什么查不到预期元素?
根本原因通常是边界语义理解偏差:subSet默认是“前闭后开”区间(fromInclusive=true, toInclusive=false),但很多人误以为两端都包含。比如set.subSet(3, 7)实际返回的是[3, 7),不包含7。
- 必须显式传参控制包含性:
subSet(3, true, 7, true)才表示[3, 7] - 如果
TreeSet用自定义Comparator,务必确保比较逻辑与范围语义一致(比如按字符串长度排序时,“长度≥5且≤10”的区间不能直接用字典序字符串做参数) - 参数值必须存在于自然排序中,或至少能被
Comparator比较——传入null或不可比对象会抛NullPointerException或ClassCastException
headSet和tailSet的截断点到底算不算在结果里?
默认都不包含截断点:headSet(5)返回所有5的元素,tailSet(5)返回所有≥5的元素。这个“≥”是唯一例外——tailSet默认包含截断点,而headSet默认不包含。
- 统一行为靠重载方法:
headSet(5, true)表示≤5,tailSet(5, false)表示>5 - 注意:这些视图是实时反射原集合的,修改原
TreeSet会立刻影响已获取的headSet结果,反之亦然 - 若原集合为空或所有元素都小于截断点,
headSet返回空集合;但若调用headSet后往原集合插入更小的元素,那些新元素**不会**出现在已持有的headSet视图中——视图只反映调用时刻的结构快照
用subSet做分页查询性能怎么样?
底层基于红黑树的ceiling/floor定位,时间复杂度O(log n),比遍历筛选快得多,但要注意它返回的是视图而非新集合。
- 反复调用
subSet本身开销小,但后续遍历视图仍是O(k)(k为子集大小),不是O(1) - 如果需要多次读取同一子集,别反复调用
subSet——缓存视图引用即可,它轻量且线程不安全,无需额外拷贝 - 跨线程共享视图前必须同步原
TreeSet,否则可能看到部分更新状态,甚至ConcurrentModificationException - 序列化视图会失败:
subSet返回的NavigableSet实现类(如TreeSet$SubSet)通常未实现Serializable,直接写入流会抛NotSerializableException
替代方案:什么时候不该用subSet?
当需求超出有序集合天然能力时,硬套subSet反而增加复杂度。
立即学习“Java免费学习笔记(深入)”;
- 需要按多个字段组合过滤(比如“价格0且上架时间在最近7天”)——用
Stream.filter更直白,哪怕牺牲一点性能 - 范围条件动态拼接(如用户输入任意起止值),且需兼容
null边界——手动处理null逻辑比依赖subSet的严格比较更可控 - 数据量极小(ArrayList配合
Collections.binarySearch手写二分查找,代码更易测、依赖更少
边界值的包含逻辑、视图的实时性、序列化限制——这三个点最容易在调试时卡住人,尤其是从文档扫一眼就开写的场景。










