HashSet

来源:互联网 发布:娶网络女主播 编辑:程序博客网 时间:2024/05/29 07:37

HashSet 是 Set接口的经典实现,它按Hash算法来存储集合中的元素,因此具有很好的存储和查找性能。HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个private static final Object PRESENT = new Object();。HashSet跟HashMap一样,都是一个存放链表的数组。


HashSet具有以下特点:

1.不能保证元素的排列顺序,顺序可能与元素的添加顺序不同,元素的顺序可能变化。

2.HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,必须通过代码来保证其同步。

3.集合元素值可以是null


实现关系

HashSet


HashSet判断两个元素相等的两个标准

1.两个对象通过equals()方法比较相等。

2.两个对象的hashCode返回值相等。

// 类A的equals方法总是返回true,但没有重写其hashCode()方法class A{    public boolean equals(Object obj)    {        return true;    }}// 类B的hashCode()方法总是返回1,但没有重写其equals()方法class B{    public int hashCode()    {        return 1;    }}// 类C的hashCode()方法总是返回2,且重写其equals()方法总是返回trueclass C{    public int hashCode()    {        return 2;    }    public boolean equals(Object obj)    {        return true;    }}public class HashSet1 {    public static void main(String[] args)    {        HashSet books = new HashSet();        // 分别向books集合中添加两个A对象,两个B对象,两个C对象        Boolean A1 = books.add(new A());        Boolean A2 = books.add(new A());        Boolean B1 = books.add(new B());        Boolean B2 = books.add(new B());        Boolean C1 = books.add(new C());        Boolean C2 = books.add(new C());    /*当HashSet中有相同对象时add()方法会返回false*/        System.out.println("A1:"+A1+"    A2:"+A2+"\nB1:"+B1+"    B2:"+B2+"\nC1:"+C1+"    C2:"+C2);    }}

输出结果

A1:true    A2:trueB1:true    B2:trueC1:true    C2:false

两个A对象添加成功,因为即使两个A对象通过equals()方法比较返回true,但是HashSet依然把他们当成两个对象。

两个B对象添加成功,因为即使两个B对象的hashCode返回相同值(都是1),但是HashSet依然把他们当成两个对象。

两个C对象一个添加成功,一个添加失败是因为当第一个C对象添加成功后books里面已经有一个C对象,无法再添加第二个相同的对象。

注意:
1.如果两个对象通过equals()方法比较返回true,但这两个对象的hashCode方法返回不同的hashCode值时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功。

2.如果两个对象的hashCode()方法返回的hashCode值相同,但它们通过equals 方法比较返回false时将会出现:两个对象的hashCode值相同,HashSet将试图把它们保存在同一个位置,但是又不行(否则将只剩下一个对象),所以实际上会在这个位置用链式结构来保存多个对象;而HashSet访问集合元素式是通过元素的hashCode值快速定位的,如果HashSet中两个以上的元素具有相同的hashCode值,将导致性能下降。

重写hashCode()方法的基本规则

  • 在程序运行过程中,同一个对象多次调用hashCode()方法应该返回相同的值。
  • 当两个对象通过equals()方法比较返回true时,这两个对象的hashCode()方法应该返回相同的值。
  • 对象中用做equals()方法比较标准的实例变量,都应该用于计算hashCode。

hashCode值的计算方式

数据类型 hashCode Boolean hashCode=(f?0:1) 整数类型(byte short int char) hashCode=(int)f hashCode=(int)f hashCode=(int)(f^(f>>>32)) float hashCode=Float.floatToIntBits(f) double long l =Double.doubleToLongBits(f)
hashCode=(int)(l^(l>>>32)) 普通引用类型 hashCode=f.hashCode()



注意
如果向HashSet中添加一个可变对象后,后面程序修改了该可变对象的实例变量,则可能导致它与集合中的其他元素相同(即两个对象通过equals()方法比较返回true,两个对象的hashCode值也相等),这就有可能导致HashSet中包含两个相同的对象。

package com.bluedot.Collection;/** * @Author: WFP * @Time: 2017/9/23 * Collection * HashSet测试javabean对象 */public class HashSetObj {    private int count;    public int getCount() {        return count;    }    public void setCount(int count) {        this.count = count;    }    public HashSetObj(int count) {        this.count = count;    }    /*最好重写toString方法,不然Stream API中输出的是该对象的ClassName*/    @Override    public String toString() {        return "HashSetObj{" +                "count=" + count +                '}';    }    /*重写equals()方法*/    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (!(o instanceof HashSetObj)) return false;        HashSetObj that = (HashSetObj) o;        return count == that.count;    }    /*重写hashCode方法,把对象内每个有意义的实例变量计算出一个int类型的hashCode值*/    @Override    public int hashCode() {        return count;    }}
package com.bluedot.Collection;import org.junit.Test;import java.util.HashSet;import java.util.Optional;/** * @Author: WFP * @Time: 2017/9/23 * Collection */public class TestHashSet {    @Test    public void test1(){        HashSet hs = new HashSet();        hs.add(new HashSetObj(5));        hs.add(new HashSetObj(-3));        hs.add(new HashSetObj(9));        hs.add(new HashSetObj(-2));        /*使用Stream API中的forEach方法遍历输出该HashSet*/        hs.stream()                .forEach(System.out::println);        System.out.println("***********************");        /*使用Stream API中的findFirst方法取出流中的第一个元素,注意findFirst方法返回的是Option*/        Optional<HashSetObj> first = hs.stream()                .findFirst();        /*为第一个元素的count赋值*/        first.get().setCount(-3);        /*再次输出HashSet,集合中有重复元素*/        hs.stream()                .forEach(System.out::println);        System.out.println("***********************");        /*删除count为-3的HashSetObj对象*/        hs.remove(new HashSetObj(-3));        /*再次输出HashSet,可以看见被删除了一个HashSet对象*/        hs.stream()                .forEach(System.out::println);        System.out.println("***********************");        System.out.println("hs是否包含了count为-3的HashSetObj对象?"+hs.contains(new HashSetObj(-3)));        System.out.println("hs是否包含了count为-2的HashSetObj对象?"+hs.contains(new HashSetObj(-2)));    }}

输出结果

HashSetObj{count=-2}HashSetObj{count=-3}HashSetObj{count=5}HashSetObj{count=9}***********************HashSetObj{count=-3}HashSetObj{count=-3}HashSetObj{count=5}HashSetObj{count=9}***********************HashSetObj{count=-3}HashSetObj{count=5}HashSetObj{count=9}***********************hs是否包含了count为-3的HashSetObj对象?falsehs是否包含了count为-2的HashSetObj对象?false

所以当程序把可变对象添加到HashSet中后,尽量不要去修改集合元素中参加计算hashCode()、equals()的实例变量,否则将会导致HashSet无法正确操作集合元素。