HashSet如何保证元素唯一性?

来源:互联网 发布:android购物类app源码 编辑:程序博客网 时间:2024/04/27 12:33

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);    }}

这里写图片描述

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是根据哈希值来排序的,那么如果有相同的哈希值的时候会发生什么情况?

再次举个例子,我们使用一个比较健全的自定义类,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,取出*/

这里写图片描述

运行结果前四句表示每一步都调用了复写完的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方法
这里写图片描述
第一句:计算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());        }    }}

运行结果:
这里写图片描述

避免了重复比较。

总结:

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

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

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

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

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

1 0
原创粉丝点击