一、核心概念与用途
特性 | HashSet | HashMap |
---|---|---|
接口实现 | 实现 Set 接口(存储唯一元素) | 实现 Map 接口(存储键值对) |
数据存储 | 存储单个对象(元素唯一) | 存储键值对(键唯一,值可重复) |
典型用途 | 去重集合(如用户 ID 集合) | 键值映射(如缓存数据、配置项) |
二、内部实现机制
-
HashSet 的底层依赖
HashSet
内部通过HashMap
实现,元素作为HashMap
的键,值使用固定虚拟对象:// HashSet源码关键字段 private transient HashMap<E, Object> map; private static final Object PRESENT = new Object(); // 虚拟值 // add方法实现 public boolean add(E e) { return map.put(e, PRESENT) == null; // 键存在则返回false }
-
HashMap 的存储结构
基于哈希表(数组 + 链表/红黑树),键通过哈希函数计算索引:// HashMap存储结构(Java 8+) transient Node<K,V>[] table; static class Node<K,V> { final int hash; final K key; V value; Node<K,V> next; }
三、功能与方法差异
操作 | HashSet 方法 | HashMap 方法 |
---|---|---|
添加元素 | add(E e) | put(K key, V value) |
删除元素 | remove(Object o) | remove(Object key) |
检查存在 | contains(Object o) | containsKey(Object key) |
获取元素 | 无直接方法(需迭代器遍历) | get(Object key) |
容量相关 | size() 返回元素数量 | size() 返回键值对数量 |
四、性能与特性对比
维度 | HashSet | HashMap |
---|---|---|
时间复杂度 | 添加/删除/查找:平均 O(1),最差 O(log n) | 同左 |
内存开销 | 较高(每个元素需额外存储虚拟值) | 较高(存储键值对) |
允许 null 值 | 允许一个 null 元素 | 允许一个 null 键和多个 null 值 |
迭代顺序 | 不保证顺序 | 不保证顺序 |
线程安全 | 非线程安全 | 非线程安全 |
五、使用场景示例
-
HashSet 适用场景
-
用户登录去重:
Set<String> loggedInUsers = new HashSet<>(); if (loggedInUsers.add(userId)) { // 首次登录处理 }
-
标签管理系统:
Set<String> uniqueTags = new HashSet<>(allTags);
-
-
HashMap 适用场景
-
缓存数据:
Map<String, Product> productCache = new HashMap<>(); productCache.put(productId, product);
-
配置项管理:
Map<String, String> configs = new HashMap<>(); configs.put("timeout", "30");
-
六、线程安全解决方案
需求 | HashSet 方案 | HashMap 方案 |
---|---|---|
同步包装 | Set<String> syncSet = Collections.synchronizedSet(new HashSet<>()); | Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>()); |
并发容器 | 无直接替代,可包装 ConcurrentHashMap :Set concurrentSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); | ConcurrentHashMap<String, String> |
七、内存与 GC 影响
-
HashSet 内存占用:
每个元素需存储键(元素对象)和固定虚拟值(约 16 字节对象头),内存开销约为元素大小的 2 倍。 -
HashMap 内存占用:
存储键值对,每个节点额外包含哈希值、指针等元数据,内存开销更高。
优化建议:
- 对
HashSet
使用-XX:+UseCompressedOops
压缩指针(64 位 JVM 默认开启) - 对
HashMap
预估初始容量,避免频繁扩容
八、扩展对比:LinkedHashSet vs LinkedHashMap
特性 | LinkedHashSet | LinkedHashMap |
---|---|---|
实现方式 | 继承 HashSet ,内部使用 LinkedHashMap | 维护插入顺序/访问顺序的双向链表 |
有序性 | 保证插入顺序 | 可配置插入顺序或访问顺序(LRU) |
性能损耗 | 略高于 HashSet (维护链表指针) | 略高于 HashMap |
九、总结
- 核心区别:
HashSet
用于存储唯一元素集合,HashMap
用于键值映射。 - 实现关联:
HashSet
基于HashMap
实现,复用其键唯一性特性。 - 选择策略:
- 需要唯一元素集合 →
HashSet
- 需要键值对存储 →
HashMap
- 需要有序 →
LinkedHashSet
/LinkedHashMap
- 高并发场景 →
ConcurrentHashMap
包装或专用并发容器
- 需要唯一元素集合 →