Set 如何保证元素不重复的?
💡 核心结论:一句话先记住
以最常用的 HashSet 为例,它自己其实是个“甩手掌柜”,底层完全是靠一个 HashMap 在帮它干活。它把你要存的元素当成 HashMap 的Key塞进去,利用“HashMap 的 Key 绝对不能重复”的铁律,顺理成章地实现了去重。
🔍 HashSet 存东西时的“两步判重法”
当你往 HashSet里执行add(元素)存东西时,底层的 HashMap 会像小安检员一样,进行以下两步高效率的检查:
第一步:对暗号(算hashCode)
安检员先拿这个元素算出一个数字暗号(哈希值)。
- 如果这个暗号在格子里从来没出现过,说明是新面孔,直接放行通过,成功存入!
- 如果发现有人的暗号一模一样(哈希冲突),别着急,它不会马上拒绝,而是启动第二步。
第二步:验真身(比equals)
因为不同东西的暗号偶尔也会撞款,安检员会把这两个暗号相同的东西揪出来,用equals()方法进行精细的“肉眼比对”:
- 比对完发现内容确实不一样:行吧,那也是新东西,找个位置存进去。
- 比对完发现内容完全一样:好家伙,真的是复制品!HashSet会直接把新来的拒之门外,返回
false(但不会报错)。
💡 为什么非要分两步?
因为
hashCode()是数字比较,计算机算起来极其飞快,能瞬间过滤掉 99% 的不重复元素;而equals()就像做全身扫描,比较慢。先用第一步大面积筛选,性能才能起飞。
🛑 工作和面试中的超级大坑
坑:只重写equals(),不重写hashCode()
如果你写了一个“自定义对象”(比如用户类),只重写了equals没重写hashCode,那 [HashSet] 的去重功能就会直接瘫痪!
因为两个一模一样的对象,算出来的暗号(hashCode)却不一样,它们会直接在第一步被分到不同的格子里,连互相见面的机会都没有,equals根本不会执行。结果就是 [Set] 里塞进去了两个完全一样的数据。
🆚 常见的三个 Set 兄弟对比
| Set 实现类 | 底层靠谁 | 怎么判重 | 出来的顺序 | 速度 |
|---|---|---|---|---|
| HashSet(盲选首选) | HashMap | hashCode+equals | 完全无序(随机乱坐) | 🚀 极快 |
| LinkedHashSet | LinkedHashMap | hashCode+equals | 先进先出(怎么放进去就怎么出来) | 🏎️ 快 |
| TreeSet | TreeMap | 靠compareTo()比较大小 | 自动从小到大排序 | 🚗 一般 |
🎯 秒记口诀
HashSet 底层 HashMap,元素做 Key 占住位。
先比 hash 快速筛,再比 equals 验真伪。
想要去重不失效,两个方法齐重写!
