Set集合实现元素不重复原理

本文深入探讨了HashSet的工作原理,包括如何通过hashCode()与equals()方法确保集合内元素的唯一性。通过对学生类实例的分析,详细解释了哈希桶的作用及元素的存储过程。

众所周知,set内容不重复。以hashSet为例,会使用对象的hashCode()与equals()来判断存入的对象是否相同。流程如下:
要存入元素,内存中开设空间,每个空间都会有对应的Hash Code,这些空间成为哈希桶(Hash Bucket)。
这里写图片描述
对想要存入HashSet回调用对象的hashCode()获取对应的哈希值,并尝试放入对应的哈希桶中,如果哈希桶中没有对象,则直接放入,如果有,会再调用对象的equals()比较。
这里写图片描述
桶中的对象调用equals()与要传入的对象进行比较,若比较结果为false,则表示两个对象非重复,可以存入,若结果为true,表示两个对象相同,不可存入。

例:
学生类(Student),为了防止收集重复对象,在Student类中复写hashCode()和equals():

 class Studen{
    private String name ;//姓名
    private int age;//年龄

    public int hashCode(){
        return name.hashCode()+age*33;
    }       
    public boolean equals(Object obj){
        if(!(obj instanceof Student))
            throw new ClassCastException("类型不匹配");
        Student s=(Student) obj;
        return this.name.equals(s.name) &&this.age==s.age;          
    }
}

如果之后出现重复对象想要被收集时,要存入对象便会和已存在容器内的对象比较,如果相同则不能再存入。

<think>我们正在讨论Java Set集合如何确保元素重复原理。根据用户提供的引用,特别是引用[2][3][4][5],我们可以总结出Set集合(以HashSet为例)确保元素重复的机制主要依赖于两个方法:hashCode()和equals()。 1. 当向Set集合添加一个元素时,首先会计算该元素的哈希值(通过hashCode()方法),然后根据哈希值计算出在哈希表中的存储位置。 2. 如果该位置上没有元素,则直接将元素添加进去。 3. 如果该位置上已经有元素(或多个元素,可能是链表或树结构),则使用equals()方法依次与该位置上的元素进行比较: - 如果与其中任意一个元素相等(即equals()返回true),则添加操作失败,元素会被加入。 - 如果都相等,则将元素添加到该位置的链表(或树)的末尾(在Java 8中,当链表长度超过阈值时会转换为红黑树)。 因此,要确保Set集合正确地判断元素是否重复,存储在Set中的对象必须正确重写hashCode()和equals()方法。否则,可能会导致重复元素被添加。 下面我们详细解释每一步,并配合引用中的例子说明。 步骤1: 计算哈希值 引用[4]中提到:“顶级父类Object中有一个hashCode方法,是用来获取哈希值的”。当我们添加一个元素时,首先调用该元素hashCode()方法计算哈希值,然后通过哈希函数(通常是对数组长度取模)得到在哈希表中的位置(桶的位置)。 步骤2: 检查位置 如果该位置为空,则直接存放新元素(引用[5])。 步骤3: 处理哈希冲突 如果该位置已经有元素(哈希冲突),则遍历该位置上的所有元素(在Java 8之前是链表,Java 8及以后当链表长度大于8时转为红黑树),用equals()方法比较新元素和已有元素是否相等: - 如果相等,则认为是重复元素添加(add()方法返回false)。 - 如果相等,则将该元素添加到链表或树中(引用[5])。 引用[3]中的自定义类MyString的例子说明了问题:因为MyString类没有重写hashCode()和equals()方法,所以即使两个MyString对象的值相同(都是"a"),也被视为同的对象。因此,s3和s4都被添加到了集合中。 而String类重写了hashCode()和equals()方法,所以s1和s2虽然是通过new创建的同对象,但内容相同,它们的哈希值相同,且equals()比较返回true,因此s2会被添加。 引用[2]也说明了同样的道理:String对象已经重写了这两个方法,所以相同的字符串会被视为重复。 因此,要保证自定义对象在Set重复,必须重写hashCode()和equals()方法。重写时需满足: - 如果两个对象通过equals()方法比较相等,那么它们的hashCode()值必须相等。 - 如果两个对象的hashCode()值相等,它们通过equals()方法比较一定相等(哈希冲突)。 总结:Set集合确保元素重复原理是基于哈希表(如HashSet)或红黑树(如TreeSet)的数据结构,并结合hashCode()和equals()方法共同判断。对于TreeSet,它使用Comparable或Comparator接口来判断元素的相等性(而hashCodeequals),但这里用户主要问的是HashSet实现机制。 注意:TreeSet依赖于比较(Comparator或Comparable)来判断元素是否重复,而HashSet和LinkedHashSet依赖于hashCodeequals。 下面我们用代码示例说明: 示例1:使用String(已正确重写hashCodeequals) HashSet<String> set = new HashSet<>(); set.add("abc"); // 添加成功 set.add("abc"); // 添加失败,返回false 示例2:自定义类未重写hashCodeequals class Person { String name; public Person(String name) { this.name = name; } } Set<Person> set = new HashSet<>(); set.add(new Person("Tom")); // 添加成功 set.add(new Person("Tom")); // 添加成功,因为两个对象的hashCode同(Object的hashCode是对象地址的映射),且equals比较为false(Object的equals比较地址) 示例3:自定义类正确重写hashCodeequals class Person { String name; // 构造方法省略 @Override public int hashCode() { return Objects.hash(name); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person other = (Person) obj; return Objects.equals(name, other.name); } } Set<Person> set = new HashSet<>(); set.add(new Person("Tom")); // 添加成功 set.add(new Person("Tom")); // 添加失败,因为两个对象的哈希值相同,且equals比较为true。 因此,对于HashSet(包括LinkedHashSet)来说,确保元素重复原理就是:先比较哈希值,再使用equals方法确认。 而对于TreeSet,它使用自然排序或自定义比较器来比较元素,当比较器返回0时认为两个元素相等(重复),所以需要重写hashCodeequals,但必须实现Comparable接口或提供Comparator。 引用[5]提到:“如果该对象的hash值存在,表示集合存在新的对象,将元素存入set集合中,如果hash值已经存在,则进一步的比较值是否相等,调用equals方法进行比较,如果值相等,说明是同一个对象,会将这个对象添加到已有对象的末尾。” 这就是HashSet的存储过程。 所以,回答用户的问题:Java中Set集合(以HashSet为例)确保元素重复原理是通过元素hashCode()和equals()方法共同作用。首先用hashCode()方法确定元素的存储位置,然后用equals()方法判断该位置上是否有重复元素。 根据引用[4]总结:“Set集合之所以重复就是因为Set集合中覆盖重写了hashCode方法和equals方法。” —— 这里指的是Java中的String等常用类已经重写了这些方法,而我们自定义类则需要按需重写。 最后,注意:在HashSet中,判断重复元素的标准是:两个元素hashCode()方法返回值相等,并且equals()方法返回true。两个条件缺一可。 如果用户需要进一步了解TreeSet原理,我们可以补充:TreeSet内部使用TreeMap(红黑树)实现,添加元素时通过比较器(Comparator)或元素的自然顺序(Comparable)进行排序和去重。当比较结果为0时,视为重复元素添加。 下面我们按照要求生成相关问题: </think>### Java Set集合确保元素重复原理 Java的Set集合通过**哈希表机制**和**对象相等性判断**共同保障元素唯一性,核心原理可分为三个步骤: #### 1. **哈希值计算与初步筛选** - 当调用`add(element)`方法时,首先计算元素的哈希值(通过`hashCode()`方法)。 - 根据哈希值定位到哈希表中的存储位置(桶位置)。 - **关键点**: 如果该位置为空(无哈希冲突),直接将元素存入[^4][^5]。 ```java // 示例:两个内容相同的String对象 String s1 = new String("abc"); String s2 = new String("abc"); System.out.println(s1.hashCode() == s2.hashCode()); // true - 哈希值相同 ``` #### 2. **哈希冲突处理与深度验证** - 若哈希值对应的位置非空(哈希冲突),则遍历该位置上的所有元素。 - 依次调用`equals()`方法比较新元素与已有元素[^2][^5]: - 若`equals()`返回`true` → 判定为重复元素,拒绝添加。 - 若`equals()`返回`false` → 判定为新元素,添加到链表末尾。 ```java Set<String> set = new HashSet<>(); set.add("通话"); set.add("重地"); // "通话"和"重地"的哈希值相同(1179395),但equals()比较同 → 均被存储 System.out.println(set); // 输出[通话, 重地] ``` #### 3. **自定义对象的特殊要求** - 对于自定义类,必须**同时重写`hashCode()`和`equals()`** 方法才能正确去重。 - **未重写的风险**: 即使对象内容相同,默认的`Object.hashCode()`基于内存地址计算,会导致重复元素被误存[^3][^4]。 ```java class CustomObject { String value; // 错误示例:未重写方法 public static void main(String[] args) { Set<CustomObject> set = new HashSet<>(); set.add(new CustomObject("A")); set.add(new CustomObject("A")); // 被当作同对象存储! System.out.println(set.size()); // 输出2 } } ``` #### ✅ 正确实现自定义类的方法 ```java @Override public int hashCode() { return Objects.hash(value); // 基于属性值计算哈希 } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CustomObject that = (CustomObject) o; return Objects.equals(value, that.value); // 比较属性值 } ``` #### 🌐 Set实现类的底层差异 | 实现类 | 底层结构 | 去重机制 | |-------------|------------|----------------------------------------| | **HashSet** | 哈希表 | 依赖`hashCode()`+`equals()` [^2][^4] | | **TreeSet** | 红黑树 | 依赖`compareTo()/Comparator`(返回0视为重复) | | **LinkedHashSet** | 链表+哈希表 | 同HashSet,增加链表维护顺序 | > ⚠️ **注意**:TreeSet通过比较器(而非`equals()`)判断重复性。当比较器返回0时视为重复元素[^5]。 ### 总结原理 1. **哈希值先行**:通过`hashCode()`快速缩小比较范围。 2. **相等性验证**:通过`equals()`精确判断对象内容是否相同。 3. **双重保障**: - `hashCode()`同 → 必定重复(直接存储) - `hashCode()`相同 → 需用`equals()`二次验证[^4][^5]。 通过这种机制,Set集合在保持$O(1)$时间复杂度的同时,高效实现元素唯一性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值