HashSet 和 HashMap 区别
| HashMap | HashSet |
|---|---|
| HashMap 实现了 Map 接口 | HashSet 实现了 Set 接口 |
| HashMap 储存键值对 | HashSet 仅仅存储对象 |
| 使用 put() 方法将元素放入 map 中 | 使用 add() 方法将元素放入 set 中 |
| HashMap 中使用键对象来计算 hashcode 值 | HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以 equals() 方法用来判断对象的相等性,如果两个对象不同的话,那么返回 false |
| HashMap 比较快,因为是使用唯一的键来获取对象 | HashSet 较 HashMap 来说比较慢 |
表格列出了 API 层区别,但最有价值的事实藏在源码里:HashSet 就是 HashMap 包了一层。理解这层包装关系,两者的一切异同都能推导出来。
源码关系:HashSet 是 HashMap 的适配器
JDK 中 HashSet 的内部实现极其简单:
private transient HashMap<E, Object> map; |
元素被当作 key 存储,value 是共享的哑对象。由此推出:
- 去重语义 = HashMap 键唯一语义
- 哈希与相等判定完全依赖元素的
hashCode/equals(对应表格第四行) - 性能特征与 HashMap 一致:增删查 O(1)
所以表格中”HashSet 较 HashMap 慢”的说法并不准确——两者同构同复杂度,HashSet 只是多一次方法转发;真正差异在用途语义而非速度。
语义与 API 差异
| 维度 | HashMap | HashSet |
|---|---|---|
| 存储 | key → value 映射 | 唯一元素集合 |
| 核心操作 | put/get/remove | add/contains/remove |
| 重复处理 | key 相同覆盖 value | add 返回 false,集合不变 |
| 遍历 | keySet/values/entrySet 三视图 | 直接迭代元素 |
| 典型问题 | “某 ID 对应什么” | “某元素在不在/去重” |
选型与常见误用
- 去重:HashSet(或 LinkedHashSet 保序);不要用 List.contains 做去重(O(n²))
- 判含/白名单/黑名单:HashSet O(1);配置项集合、权限点集合的典型结构
- 需要附带数据:HashMap(元素 → 附加信息);若附加信息只是”存在性”,HashSet 更省内存(无 value 存储,虽然底层仍有 PRESENT 占位,但语义清晰)
- 交并差集合运算:HashSet 的 addAll/retainAll/removeAll 语义直接;HashMap 需手动遍历键集
- 两者都依赖 equals/hashCode 契约:自定义对象入 Set 前必须成对重写,否则”去重失效”(哈希不同导致同一逻辑对象重复入集)
家族对应关系
HashMap 与 HashSet 的变体一一对应,选型时可平行迁移:
- HashMap ↔ HashSet(无序 O(1))
- LinkedHashMap ↔ LinkedHashSet(插入序)
- TreeMap ↔ TreeSet(排序序 O(logN))
- ConcurrentHashMap ↔
ConcurrentHashMap.newKeySet()(并发集合,JDK8 起推荐替代 CopyOnWriteArraySet 的大集合场景)
记住”HashSet = HashMap 的键集合视图”这一个事实,表格里的五行区别就都不需要背了:接口不同是因为语义不同,方法不同是因为操作对象不同,而哈希与相等契约、复杂度、变体家族则完全共享。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 非鱼小站!
评论
WalineDisqus











