HashSet

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_第1张图片


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()方法总是返回true
class 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:true
B1:true    B2:true
C1: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 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对象?false
hs是否包含了count为-2的HashSetObj对象?false

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

你可能感兴趣的:(HashSet,对象,集合,lambda,stream,java)