一、底层实现与数据结构
| 类别 | HashSet | TreeSet |
|---|---|---|
| 底层结构 | 哈希表(数组+链表+红黑树) • JDK8后链表长度≥8转为红黑树 • 通过hashCode()计算位置 |
红黑树(自平衡二叉搜索树) • 通过compareTo()或Comparator确定位置 • 不允许null元素 |
| 元素顺序 | 无序(随机) | 有序(自然排序/自定义排序) |
| 去重依据 | hashCode() + equals() |
compareTo() 或 Comparator(比较结果为0即视为重复) |
| 时间复杂度 | O(1)(无冲突时) • 冲突时降为O(log n) | O(log n) • 所有操作均为此复杂度 |
| null支持 | 允许(仅1个) | 不允许(抛出NullPointerException) |
| 核心优势 | • 极致性能的去重操作 • 高频插入/删除/查找 | • 自动排序功能 • 支持范围查询(如subSet()) • 可获取最大/最小元素 |
| 典型场景 | • 缓存系统 • 过滤重复数据 • 不关心顺序的集合 | • 排行榜 • 按时间排序的任务队列 • 需范围查询的场景(如分数区间) |
| 关键要求 | • 必须重写hashCode()和equals() • 建议预设初始容量避免扩容 |
• 元素需实现Comparable或提供Comparator • 避免频繁结构修改 |
| 常见误区 | • 未重写hashCode()导致去重失效 • 忽略负载因子导致频繁扩容 |
• 尝试存储null元素 • 误以为equals()决定去重(实际由比较器决定) |
| 优化建议 | • 设置合理初始容量(如new HashSet<>(1000)) • 确保hashCode()分布均匀 |
• 使用Comparator.nullsFirst()处理null • 批量操作用addAll()替代循环add() |
| 线程安全方案 | Collections.synchronizedSet() |
ConcurrentSkipListSet(替代品) |
二、核心特性对比
| 特性 | HashSet | TreeSet |
|---|---|---|
| 底层结构 | 哈希表(数组+链表+红黑树) | 红黑树(自平衡二叉搜索树) |
| 元素顺序 | 无序(随机) | 有序(自然排序或自定义排序) |
| 去重依据 | hashCode() + equals() |
compareTo() 或 Comparator |
| null支持 | 允许(仅1个) | 不允许(会抛出NPE) |
| 时间复杂度 | O(1)(无冲突) | O(log n) |
| 内存占用 | 较低 | 较高(需存储父节点、颜色标记等) |
| 线程安全 | 否 | 否 |
| 特殊功能 | 无 | 范围查询、获取最大/最小元素等 |
三、TreeSet排序规则
| 排序方式 | 依据 | 规则 | 示例 |
|---|---|---|---|
| 自然排序(默认) | 元素实现 Comparable<E> 接口的 compareTo(E o) 方法 |
- 返回负数:当前元素小,排在前面 - 返回 0:视为重复元素,不添加 - 返回正数:当前元素大,排在后面 | - Integer:按数值大小升序 - String:按字典序升序 - 自定义对象需实现 Comparable 接口 |
| 定制排序 | 构造时传入 Comparator<? super E> 接口实现 |
- compare(o1, o2) 返回负数:o1 小于 o2,o1 排前 - 返回 0:视为重复,不添加 - 返回正数:o1 大于 o2,o2 排前 |
- 按字符串长度排序: (s1, s2) -> Integer.compare(s1.length(), s2.length()) - 按对象属性排序: (p1, p2) -> p1.getName().compareTo(p2.getName()) |
| 多级排序 | 使用 Comparator.thenComparing() 链式调用 |
- 先按主字段比较 - 主字段相等时,再按次字段比较 | Comparator<Person> comp = Comparator.comparingInt(Person::getAge).thenComparing(Person::getName); |
| → 先按年龄升序,年龄相同按姓名字典序 | |||
| 降序排序 | 使用 Comparator.reverseOrder() 或自定义比较逻辑 |
- 交换比较对象位置实现降序 - 或使用反向比较器 | - 降序字符串: new TreeSet<>(Comparator.reverseOrder()) - 降序整数: (n1, n2) -> n2 - n1 |
四、性能特点详解
- HashSet性能
- 优势:在无哈希冲突情况下,插入、删除、查找操作均达到O(1)时间复杂度
- 瓶颈:哈希冲突时性能下降,链表长度>8时转为红黑树,性能变为O(log n)
- 优化建议:
- 预估数据量,设置合适的初始容量(避免扩容)
- 合理设置负载因子(默认0.75)
- 确保hashCode()分布均匀,减少冲突
- TreeSet性能
- 优势:提供有序存储,支持范围查询(如查找大于某值的元素)
- 瓶颈:所有操作时间复杂度为O(log n),比HashSet慢一个数量级
- 优化建议:
- 对于自定义对象,提供高效的Comparator
- 避免频繁的结构修改(会触发树的重新平衡)
- 大数据集下考虑使用NavigableSet接口提供的范围查询功能
五、适用场景与选择指南
-
优先选择HashSet的场景
- 需要极致性能的去重操作
- 不关心元素顺序,仅需判断元素是否存在
- 高频的插入、删除、查找操作(如缓存系统、过滤重复数据)
- 允许存储null元素的场景
-
优先选择TreeSet的场景
- 需要自动排序的去重集合(如排行榜、按时间排序的任务队列)
- 需要范围查询功能(如查找分数在80-90之间的学生)
- 需要获取最大/最小元素(如first()、last()方法)
- 元素具有自然顺序或需要自定义排序规则