一句话总结:
- HashSet 像 乱序的储物柜,存取快但不关心顺序。
- TreeSet 像 自动整理的书架,元素按顺序排列但操作稍慢。
一、核心区别对比
| 对比项 | HashSet | TreeSet |
|---|---|---|
| 底层实现 | 基于哈希表(HashMap) | 基于红黑树 |
| 元素顺序 | 无序 | 自然排序或自定义排序(通过比较器) |
| 查询速度 | O(1)(平均) | O(log n) |
| 插入速度 | O(1)(平均) | O(log n) |
| 内存占用 | 更低(仅存元素和哈希值) | 更高(每个节点存左右子节点指针) |
| 额外功能 | 无 | 支持范围查询(如 subSet(), tailSet()) |
二、使用场景
1. 用 HashSet 当:
-
需求:快速存取,不关心顺序
-
示例场景:
- 存储唯一用户名,快速判断用户是否存在。
- 缓存临时数据,无需排序。
Set<String> usernames = new HashSet<>(); usernames.add("张三"); if (usernames.contains("李四")) { /* 检查是否存在 */ } -
2. 用 TreeSet 当:
-
需求:元素自动排序,或需要范围查询
-
示例场景:
-
存储成绩单,按分数从高到低排序。
-
查找某个范围内的数据(如年龄在20-30岁的用户)。
-
Set<Integer> scores = new TreeSet<>((a, b) -> b - a); // 降序排序 scores.add(90); scores.add(85); // 输出:[90, 85] // 范围查询:获取 >=80 且 <90 的分数 SortedSet<Integer> subScores = ((TreeSet<Integer>)scores).subSet(80, 90); -
三、性能取舍
-
HashSet 更快,但无序:
- 适合频繁插入、删除、判断是否存在。
- 不关心顺序时优先选它。
-
TreeSet 有序,但稍慢:
- 适合需要排序或范围操作的场景。
- 用自定义对象时,对象必须实现
Comparable或传入Comparator。
四、经典例子
1. 存储不重复的随机数
-
用 HashSet:
Set<Integer> randomNumbers = new HashSet<>(); while (randomNumbers.size() < 10) { randomNumbers.add((int)(Math.random() * 100)); } // 结果:10个不重复的随机数(无序)
2. 维护排行榜
-
用 TreeSet:
Set<Player> leaderboard = new TreeSet<>((p1, p2) -> p2.score - p1.score); leaderboard.add(new Player("Alice", 100)); leaderboard.add(new Player("Bob", 95)); // 结果:按分数降序排列
五、总结口诀
「HashSet 快如风,存取无序效率高
TreeSet 自动排,范围查询是绝招
要快还是要顺序,根据场景选对宝!」