TreeSet 保证有序但需实现 Comparable 或传 Comparator,底层红黑树;HashSet 查插快但无序,底层哈希表;选型需据排序需求、顺序要求、并发场景综合判断。

TreeSet 保证有序,但得实现 Comparable 或传 Comparator
TreeSet 内部用红黑树实现,插入、查找、遍历天然按元素自然顺序(或自定义顺序)排列。但前提是元素类型必须能比较大小——要么实现 Comparable 接口(比如 String、Integer),要么构造时显式传入 Comparator。
常见错误现象:ClassCastException,比如往空 TreeSet 里加 new Object() 或没实现 Comparable 的自定义类;或者用 lambda 写 Comparator 时返回值不是 int(比如写了 return a.name > b.name 这种布尔表达式)。
- 如果业务逻辑强依赖「每次遍历都升序」,且元素类型天然可比(如时间戳、编号),优先选
TreeSet - 若需按非自然字段排序(比如按用户昵称长度),用
new TreeSet(Comparator.comparingInt(u -> u.getName().length())) - 注意:
TreeSet不允许null元素(除非用自定义Comparator显式处理null)
HashSet 查得快、插得快,但顺序完全不保证
HashSet 底层是哈希表,平均时间复杂度 O(1),但不维护任何顺序——你 add 的顺序、迭代出来的顺序、甚至两次运行的顺序都可能不同。JDK 8 后内部用了红黑树优化链表过长的情况,但对外仍不承诺顺序。
常见错误现象:本地测试时遍历结果“看起来有序”,上线后逻辑出错;或者误以为 HashSet 能替代 LinkedHashSet 来保插入顺序。
立即学习“Java免费学习笔记(深入)”;
- 只关心“去重”和“快速查存”,不依赖顺序 → 无脑选
HashSet - 需要保持插入顺序?换
LinkedHashSet,它多一个双向链表,开销略增但顺序稳定 - 元素是自定义类?务必重写
hashCode()和equals(),否则add()可能重复、contains()返回 false
性能差距在数据量大时才明显,小集合别纠结
100 个元素以内,TreeSet 和 HashSet 的实际耗时差异几乎感知不到。真正拉开差距的是操作频率和数据规模:
-
HashSet:单次add/contains平均 O(1),最坏 O(n)(哈希冲突严重时) -
TreeSet:单次add/contains稳定 O(log n),n=100 时约 7 次比较,n=10000 时约 14 次 - 遍历
TreeSet是 O(n),但自带排序;遍历HashSet也是 O(n),但得额外Arrays.sort()才能有序,总成本更高
所以别一上来就压测,先想清楚:你是不是真的每秒要跑几万次 add/contains?还是只是初始化一次、后续只读遍历?
别忽略内存和线程安全的隐性成本
TreeSet 每个节点要存左右子节点引用,比 HashSet 的哈希桶节点更占内存;而 HashSet 在扩容时会触发数组复制,可能引发短暂停顿。
两者都不支持并发修改:多线程环境下直接用会抛 ConcurrentModificationException。
- 需要线程安全的有序 Set?用
Collections.synchronizedSortedSet(new TreeSet()),但注意迭代时仍需手动同步 - 高并发场景更推荐
ConcurrentSkipListSet(基于跳表,支持并发且有序),但它不接受null,且 comparator 必须能处理所有元素 - 如果只是偶尔写、频繁读,考虑用不可变集合(如
ImmutableSortedSetfrom Guava)避免锁开销
排序需求看着简单,但 Comparable 的 null 处理、Comparator 的稳定性、并发下的可见性——这些地方一漏,问题往往出现在压测后期或流量高峰时。









