深入解析Java:HashSet是如何保证元素不重复的 深入解析JavaHashSet是如何保证元素不重复的前言一、核心前置知识HashSet的底层实现1.1 基础定义1.2 底层依赖HashSet 本质就是 HashMap源码核心结论二、核心原理HashMap 如何保证 Key 不重复2.1 判断规则两步校验核心公式2.2 为什么需要同时比较 hashCode 和 equals三、流程详解向HashSet添加元素的完整过程3.1 详细执行步骤3.2 可视化流程图四、源码佐证HashMap put 方法核心去重逻辑源码对应 HashSet 逻辑五、关键面试考点自定义对象存入HashSet5.1 问题演示5.2 原因分析5.3 解决方案重写 hashCode() 和 equals()六、总结HashSet 去重核心知识点结束语The Begin点点关注收藏不迷路⬇ ⬇ 底部 ⬇ ⬇前言在Java的集合框架中HashSet是我们日常开发中最常用的去重集合工具它能高效存储唯一元素杜绝重复数据插入。但很多开发者只知其然不知其所以然HashSet底层究竟是如何实现去重的为什么自定义对象存入HashSet必须重写hashCode()和equals()方法本文将从源码剖析、底层原理、执行流程、流程图四个维度深度拆解HashSet保证元素不重复的核心逻辑彻底吃透这个经典面试高频考点。一、核心前置知识HashSet的底层实现1.1 基础定义HashSet 是 Java 集合框架的成员实现了Set接口核心特性不允许存储重复元素无序不保证元素存储顺序和插入顺序一致允许存储null值仅能存一个非线程安全。1.2 底层依赖HashSet 本质就是 HashMap这是理解HashSet去重的第一关键点HashSet 没有自己独立的数据结构底层完全依赖 HashMap 实现。我们直接看java.util.HashSet核心源码JDK1.8publicclassHashSetEextendsAbstractSetEimplementsSetE,Cloneable,java.io.Serializable{// 底层存储数据的HashMapHashSet的元素都存在这个map中privatetransientHashMapE,Objectmap;// 虚拟的Object对象作为HashMap的value所有key共享一个valueprivatestaticfinalObjectPRESENTnewObject();// 无参构造初始化底层的HashMappublicHashSet(){mapnewHashMap();}// 核心add方法直接调用HashMap的put方法publicbooleanadd(Ee){// 关键点把我们要添加的元素 作为HashMap的key固定值PRESENT作为valuereturnmap.put(e,PRESENT)null;}}源码核心结论HashSet 内部维护了一个HashMapE, Object实例调用hashSet.add(元素)时元素会被作为 HashMap 的 key存入HashMap 的所有 key 都共用一个固定的虚拟对象PRESENT作为 valueHashSet 去重本质就是 HashMap 的 key 去重。二、核心原理HashMap 如何保证 Key 不重复既然 HashSet 依赖 HashMap 的 key 去重那我们就要搞懂HashMap 是如何判断两个 key 是否重复的2.1 判断规则两步校验核心公式HashMap 判断两个 key 完全相等的唯一标准两个 key 的 hashCode 值相等 且 equals() 方法返回 true两个条件缺一不可执行顺序第一步优先比较两个对象的hashCode()值快速筛选第二步如果 hashCode 相同再通过equals()方法比较内容只有两者都满足才判定为重复元素拒绝插入否则判定为不同元素。2.2 为什么需要同时比较 hashCode 和 equalshashCode 作用快速定位元素在哈希表中的存储位置数组下标效率极高hashCode 缺陷不同对象可能产生相同的 hashCode哈希冲突equals 作用精准校验对象内容解决哈希冲突带来的误判问题。✅ 总结hashCode 是快速筛选equals 是最终精准校验两者结合既保证效率又保证准确性。三、流程详解向HashSet添加元素的完整过程结合源码我们把HashSet.add()的执行流程拆解为7个核心步骤清晰展示去重逻辑3.1 详细执行步骤调用HashSet.add(元素)方法HashSet 底层调用HashMap.put(key元素, valuePRESENT)HashMap 计算待添加 key 的hashCode()值第一步校验比较哈希表中是否存在相同 hashCode 的 key不存在 → 直接插入新元素返回true存在 → 进入下一步精准校验第二步校验调用key.equals(已有key)比较对象内容若equals返回true→ 判定为重复元素 → 拒绝插入 → 返回false若equals返回false→ 判定为新元素 → 解决哈希冲突后插入 → 返回true。3.2 可视化流程图开始调用HashSet.add(obj)底层执行HashMap.put(obj, PRESENT)计算待添加对象的hashCode值HashMap中存在相同hashCode的key?直接插入新元素, HashSet.add()返回true执行key1.equals(key2)比较内容equals返回true?判定重复, 拒绝插入, add()返回false哈希冲突, 链表/红黑树存储, 插入成功, add()返回true结束四、源码佐证HashMap put 方法核心去重逻辑为了让原理更有说服力我们看HashMap.put()核心去重源码JDK1.8finalVputVal(inthash,Kkey,Vvalue,booleanonlyIfAbsent,booleanevict){// ... 省略哈希表初始化代码if((ptab[i(n-1)hash])null)// 无哈希冲突直接插入tab[i]newNode(hash,key,value,null);else{// 存在哈希冲突开始判断key是否重复NodeK,Ve;Kk;// 核心先比较hash值再比较equalsif(p.hashhash((kp.key)key||(key!nullkey.equals(k))))// hash相同且equals相同 → 重复key → 覆盖valueep;// ... 省略红黑树、链表插入逻辑if(e!null){VoldValuee.value;// 重复key返回旧valuereturnoldValue;}}returnnull;}源码对应 HashSet 逻辑HashMap 插入重复 key 时会返回旧的 valueHashMap 插入新 key 时返回null回到 HashSet 的 add 方法return map.put(e, PRESENT)null;put 返回 null → 插入成功 → 返回 trueput 返回非 null → 重复元素 → 返回 false。五、关键面试考点自定义对象存入HashSet这是开发和面试中最容易踩坑的点如果不重写 hashCode() 和 equals()自定义对象无法被 HashSet 去重5.1 问题演示定义一个 User 类不重写任何方法classUser{privateStringname;publicUser(Stringname){this.namename;}}publicclassTest{publicstaticvoidmain(String[]args){HashSetUsersetnewHashSet();set.add(newUser(张三));set.add(newUser(张三));// 预期1个元素实际输出2System.out.println(set.size());}}5.2 原因分析Object 类的hashCode()根据对象内存地址计算两个 new 出来的对象地址不同 → hashCode 不同第一步校验直接判定为不同元素直接插入最终导致内容相同的对象被判定为重复元素。5.3 解决方案重写 hashCode() 和 equals()classUser{privateStringname;publicUser(Stringname){this.namename;}// 重写hashCode根据对象内容计算哈希值OverridepublicinthashCode(){returnObjects.hash(name);}// 重写equals根据对象内容比较Overridepublicbooleanequals(Objectobj){if(thisobj)returntrue;if(objnull||getClass()!obj.getClass())returnfalse;Useruser(User)obj;returnObjects.equals(name,user.name);}}✅ 重写后两个new User(张三)会被判定为重复元素HashSet 成功去重。六、总结HashSet 去重核心知识点底层依赖HashSet 底层是 HashMap元素存储为 HashMap 的 keyvalue 是固定虚拟对象去重标准hashCode() 相同 equals() 返回 true缺一不可执行流程先算 hashCode 快速定位再用 equals 精准校验开发规范自定义对象存入 HashSet必须同时重写hashCode()和equals()效率优势哈希表结构保证了 O(1) 级别的插入、查询效率。结束语HashSet 的去重原理是 Java 集合的基础核心本质是对 HashMap key 去重的封装。理解了「hashCode 快速筛选 equals 精准校验」的逻辑不仅能轻松应对面试更能在实际开发中避免重复数据的坑。建议大家动手调试源码结合流程图加深理解彻底掌握这个知识点The End点点关注收藏不迷路⬆ ⬆ 顶部 ⬆ ⬆