subset默认左闭右开,tailset包含起点而headset不包含终点;三者均返回动态视图,非快照,修改原集合会实时反映;四参数重载自java 7起支持,null作边界需比较器明确处理。

subSet 用法和边界陷阱
subSet 看似简单,但边界行为极易出错:它默认是「左闭右开」,即 subSet(fromElement, toElement) 包含 fromElement,但不包含 toElement。如果你想要闭区间,得显式传入 true 和 false 控制 inclusivity。
- 常见错误现象:
treeSet.subSet(3, 7)查不到值为7的元素,哪怕7明明在集合里 - 正确写法(闭区间):
treeSet.subSet(3, true, 7, true) - 注意:
fromElement必须 ≤toElement,否则抛IllegalArgumentException - 如果
fromElement不在集合中,只要存在比它大的最小元素,查询仍有效;但若整个集合都小于fromElement,返回空视图
tailSet 和 headSet 的“起点是否包含”必须说清
tailSet 和 headSet 默认也带边界逻辑——tailSet(fromElement) 是「包含 fromElement」的,而 headSet(toElement) 是「不包含 toElement」的。这个不对称性常被忽略。
- 使用场景:分页加载、实时监控中取「大于等于某时间戳的所有日志」用
tailSet(timestamp, true)更安全(显式声明) - 参数差异:
tailSet(e)等价于tailSet(e, true);headSet(e)等价于headSet(e, false) - 性能影响:三者都返回的是原
TreeSet的视图(view),非拷贝,修改视图会同步反映到原集合,但遍历复杂度仍是 O(k),k 是子集大小
范围查询结果是动态视图,不是快照
所有 subSet、tailSet、headSet 返回的都是 NavigableSet 视图,底层共享同一棵红黑树。这意味着它们不是一次性快照。
- 常见错误现象:先调用
subSet(10, 20)得到一个子集,再往原TreeSet插入15,子集里立刻出现15;同理,删掉12,子集也跟着丢 - 如果你需要稳定快照,必须手动转成新集合:
new TreeSet(treeSet.subSet(10, 20)) - 兼容性注意:Java 6 引入
subSet两参数重载,Java 7+ 才支持四参数(含边界控制),老项目需检查 JDK 版本
null 值和自定义比较器下的行为雷区
TreeSet 本身不允许 null(除非用了自定义 Comparator 且明确处理 null),但一旦用了自定义比较器,subSet 等方法对边界元素的比较逻辑就完全取决于你写的 compare 方法。
- 错误示例:自定义比较器里没处理
null,却用subSet(null, someValue)→ 直接NullPointerException - 安全做法:边界元素必须能被比较器合法比较;若比较器允许
null在前/后,那headSet(null)可能返回全部或空,取决于你的实现 - 建议:避免用
null作范围边界;如需表示“无下界”,直接用treeSet全量;“无上界”用tailSet(minValue)或全量更清晰










