hashset底层基于hashmap实现,元素作为key、present对象作为value;其去重依赖元素的hashcode()和equals()方法,未重写时默认按内存地址判等。

Java 中的 HashSet 并不直接存储元素,而是用一个内部封装的 HashMap 来实现,其中你添加的每个元素作为 HashMap 的 key,而 value 固定为一个共享的、无意义的静态对象 PRESSENT(即 new Object())。
HashSet 本质是 HashMap 的“键专用视图”
HashSet 的源码里有这样一行关键声明:
它不是自己管理数组或链表,而是把所有增删查操作委托给这个 map。比如:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
add(e)实际调用map.put(e, PRESENT) -
remove(e)实际调用map.remove(e) -
contains(e)实际调用map.containsKey(e) -
size()返回map.size()
为什么用 Object 作 value?
因为 HashMap 的核心机制是靠 key 的 hashCode() 和 equals() 定位和判等,value 只是附带存储的数据。HashSet 不关心 value 是什么,只要能占位、不干扰哈希逻辑即可。JDK 选择一个私有的、不可变的空对象 PRESENT,既节省内存(所有 entry 共享同一个实例),又避免装箱/字符串等额外开销。
底层结构完全复用 HashMap 的哈希表 + 链表/红黑树
当你往 HashSet 添加元素时,实际发生的是:
- 计算元素
e.hashCode(),映射到哈希桶索引 - 若桶为空,新建
Node存入(e, PRESENT) - 若桶非空,遍历链表或红黑树,用
e.equals()比较已存在 key;相同则覆盖 value(无实际影响),不同则插入新节点 - 当链表过长(≥8)且 table 长度 ≥64,链表转为红黑树以保证查找效率
注意:Set 的去重能力完全取决于元素自身的 equals/hashCode
因为 HashSet 把元素全交给 HashMap 当 key 处理,所以:
- 两个对象
o1.equals(o2) == true⇒ 它们的hashCode()必须相等(否则可能存入不同桶,导致重复) - 若没重写
hashCode()和equals(),默认使用Object的实现(基于内存地址),那即使内容相同也会被当作不同元素 - 自定义类放入
HashSet前,必须确保正确重写这两个方法
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










