HashSet如何保证元素唯一性?

时间:2021-06-13 16:20:01

Collection中的Set分为HashSet和TreeSet
Set中的元素是无序的,即存入和取出的顺序不一定一致元素不可以重复。HashSet的底层数据结构是哈希表,元素存入的顺序是按照哈希值来排序的。
那么HashSet是如何保证元素唯一性的呢?

首先我们先直观地看看哈希值的样子

 class Demo{
}

public class HashSetTest {

public static void main(String[] args) {
Demo d1=new Demo();
Demo d2=new Demo();
sop(d1);
sop(d2);
}
public static void sop(Object obj){//定义此sop方法用来快捷输出,在输出较多的情况下尤为快捷。
System.out.println(obj);
}
}

HashSet如何保证元素唯一性?

HashCode就长这个样子

这些HashCode是如何产生的呢?
HashSet有一个方法叫做 int hashCode(),会在默认情况下产生无序的16进制hashcode值。
为了让其得到体现,我们可以直接将其重写:
在Demo类中重写hashCode()方法

 class Demo{
public int hashCode()
{
return 60;
}
}

public class HashSetTest {

public static void main(String[] args) {
Demo d1=new Demo();
Demo d2=new Demo();
sop(d1);
sop(d2);
}
public static void sop(Object obj){
System.out.println(obj);
}
}

重写的效果就是,不管什么情况下,hashCode()方法都只返回一个hashCode值,60(16进制下为3c),所以其结果如下:

HashSet如何保证元素唯一性?

那么问题来了,由于HashSet是根据哈希值来排序的,那么如果有相同的哈希值的时候会发生什么情况?

再次举个例子,我们使用一个比较健全的自定义类,Person,此类有两个元素,String name和 int age,还有getName(),getAge()两个基础方法,再加上复写的hashCode()方法。定义HashSet对象hs。

class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name=name;
this.age=age;
}

public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public int hashCode()
{
System.out.println(this.name+"......hashCode");
return 60;
}
}

public class HashSetDemo {

public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args) {
HashSet hs=new HashSet<>();

hs.add(new Person("a1",11));
hs.add(new Person("a2",12));
hs.add(new Person("a2",12));
hs.add(new Person("a3",13));

Iterator it=hs.iterator();
while(it.hasNext()){
Person p=(Person)it.next();
sop(p.getName()+"::"+p.getAge());
}
}

}
/*思路:
* 1,对人描述,将数据封装进人对象
* 2,定义容器,将人存入
* 3,取出
*/

HashSet如何保证元素唯一性?

运行结果前四句表示每一步都调用了复写完的hashCode()方法计算哈希值,后四句表示将a1,a2,a3都写入和新容器,且a2写了两次。

这个情况就是所有的a对象(转型到了hashSet),他们的hashcode都是一样的,依旧无法避免他们写入,没有保证元素的唯一性。

因为HashSet()保证元素唯一性是通过元素的两个方法,hashCode()和equals()来完成的。
如果元素的HashCode值相同,才会判断equals是否为true。
如果元素的HashCode值不同,不会调用equals。

由于原始equals方法只适用于String的判断,不能识别name和age的组合,所以会出现重复的元素写入。

让我们重写一个适用于Person的equals方法

public boolean equals(Object obj)
{

if(!(obj instanceof Person))
return false;
Person p=(Person)obj;
System.out.println(this.name+"...."+p.name);
return this.name.equals(p.name) && this.age == p.age;
}

此方法实现了保证name和age都相同时视为重复元素。在hashCode()方法运行时会自动调用equals方法
HashSet如何保证元素唯一性?
第一句:计算a1哈希值,存入
第二句:计算a2哈希值,发现和a1一样为3c
第三句:a2和a1进行equals比较,不一样,存入
第四句:第二个a2来了,计算哈希值也是3c
第五句:第二个a2和第一个a2equals比较,一样,不存入
第六句:a3计算哈希值,为3c
七八句为a3和与其哈希值相同的a1,a2比较equals。
九,十,十一就是存入的结果,没有重复。
其实这是一种比较尴尬的状态,因为所有的元素哈希值都是一样的,这就意味着第2个元素要和第一个比较,第3个要和1,2比较,第4个要和1,2,3比较,第5个要和1,2,3,4比较,效率较低

所以做个优化,将hashCode()方法的return 60;换成return name.hashCode()+age*37;(37为随意取值)

完整代码

import java.util.*;

class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name=name;
this.age=age;
}
public int hashCode()
{
System.out.println(this.name+"......hashCode");
return name.hashCode()+age*37;
}

public boolean equals(Object obj)
{

if(!(obj instanceof Person))
return false;
Person p=(Person)obj;
System.out.println(this.name+"....equals...."+p.name);
return this.name.equals(p.name) && this.age == p.age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}

public class HashSetDemo {

public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args) {
HashSet hs=new HashSet<>();

hs.add(new Person("a1",11));
hs.add(new Person("a2",12));
hs.add(new Person("a2",12));
hs.add(new Person("a3",13));

Iterator it=hs.iterator();
while(it.hasNext()){
Person p=(Person)it.next();
sop(p.getName()+"::"+p.getAge());
}
}

}

运行结果:
HashSet如何保证元素唯一性?

避免了重复比较。

总结:

Set:元素无序(存入和取出的顺序不一定一致),元素不可以重复。

  • HashSet:底层数据结构是哈希表。

  • HashSet如何保证 元素唯一性的呢?
    是通过元素的两个方法,hashCode和equals来完成的。
    如果元素的HashCode值相同,才会判断equals是否为true。
    如果元素的HashCode值不同,不会调用equals。

注意,对于判断元素是否存在,以及删除等操作,依赖的方法是元素的HashCode和equals方法
先判断hashcode是否相同,如果相同再判断equals

!!(ArrayList判断和删除元素只依赖equals)