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;
private static final Object PRESENT = new Object(); // 所有键共用的占位值

public boolean add(E e) {
return map.put(e, PRESENT) == null; // 元素作 key,值永远是 PRESENT
}
public boolean contains(Object o) { return map.containsKey(o); }
public boolean remove(Object o) { return map.remove(o) == PRESENT; }

元素被当作 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 的键集合视图”这一个事实,表格里的五行区别就都不需要背了:接口不同是因为语义不同,方法不同是因为操作对象不同,而哈希与相等契约、复杂度、变体家族则完全共享。