HashSet类源码详解

一、简介

1.存储结构

HashSet 的底层是利用HashMap(邻接表结构)进行存储的。

hashSet 将存入的元素作为 hashMap的key,而所有key对应的value都是一个用static final 修饰的,

名为PRESENT的Object 类对象(此对象有地址引用,但是实际内容为空)。

·存储结构如图所示:
在这里插入图片描述

2.存储特点

因为我们存入的元素作为了HashMap的键key,而HashMap中的键key是要求不重复的,

所以,我们存入到HashSet中的元素 ,也是无序不重复的。

需要注意的是:

在往HashMap中put时,如果查找发现键key是重复的,会进行值value的覆盖。

而在HashSet中,如果查找发现,新加元素是重复元素,不进行任何操作(即不进行覆盖更新)。

3.HashSet的无重复原则

首先我们应该知道:
·JAVA中提供的数据类型( 8个基本数据类型+引用数据类型String),由于底层重写了equals()方法,比较方式由原来Object类中默认的“==”方式,改成了 值比较的方式。所以,这里的9个数据类型,通过equals()方法进行的是值比较。
·而我们自己通过Class关键字定义的类所创建的类对象,是否相同是通过equals()方法(默认" == "的方式)进行比较的。由于equals()方法没有重写,内部默认的比较方式是 “==” ,所以默认比较的是对象的地址引用。
·综上所述,我们需要知道,若HashSet想要实现往里面添加的对象是不重复的,就需要重写equals()和hashCode()方法。

4.基本方法

有: 增(add)、 删(remove)。
无: 改(set)、 查(get)。

二、源码分析

1.继承的父类&实现的接口

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

2.重要属性

//序列化ID:表示此类允许被序列化和反序列化
    static final long serialVersionUID = -5024744406713321676L;

    //HashSet的底层,是利用此HashMap的类对象map进行数据存储的
    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    //与后台映射对象关联的虚拟值
    //我们存入的所有元素都作为了HashMap中的键key,
    // 而所有key对应的value值,存的都是这这Object类对象。
    private static final Object PRESENT = new Object();

3.构造方法

1>.无参构造

 /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     * 
     * 构造一个新空集;
     * 后台的HashMap实例具有默认的初始容量(16)和负载因子(0.75)。
     */
    public HashSet() {
        map = new HashMap<>();
    }

2>.有参构造

1.HashSet(int initialCapacity)方法:
/**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * the specified initial capacity and default load factor (0.75).
     * 
     * 构造一个新空集;
     * 后台的HashMap实例具有指定的初始容量和默认负载因子(0.75)。
     *
     * @param      initialCapacity   the initial capacity of the hash table
     * @throws     IllegalArgumentException if the initial capacity is less
     *             than zero
     */
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }
2.HashSet(int initialCapacity, float loadFactor) 方法:
/**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * the specified initial capacity and the specified load factor.
     *
     * 构造一个新空集;
     * 后台的HashMap实例具有指定的初始容量和指定的负载因子。
     *
     * @param      initialCapacity   the initial capacity of the hash map
     * @param      loadFactor        the load factor of the hash map
     * @throws     IllegalArgumentException if the initial capacity is less
     *             than zero, or if the load factor is nonpositive
     */
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

3>.利用集合构造集合

/**
     * Constructs a new set containing the elements in the specified
     * collection.  The <tt>HashMap</tt> is created with default load factor
     * (0.75) and an initial capacity sufficient to contain the elements in
     * the specified collection.
     * 
     * 构造包含指定集合中所有元素的新集合。
     * 创建的HashMap带有默认负载因子(0.75)和初始容量,
     * 初始容量足以包含指定集合中的元素。
     *
     * @param c the collection whose elements are to be placed into this set
     * @throws NullPointerException if the specified collection is null
     */
    public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

4.有“增”和“删”(无“改”和“查”)

1.增加add(E e):

	public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

2.删除remove(Object o):

	public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

5.其他方法

1>.iterator()方法:

	//返回此集合的元素迭代器
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

2>.clone()方法:

/**
     * Returns a shallow copy of this <tt>HashSet</tt> instance: the elements
     * themselves are not cloned.
     * 
     * 返回这个HashSet实例的浅副本:元素本身没有克隆。
     *
     * @return a shallow copy of this set
     */
    @SuppressWarnings("unchecked")
    public Object clone() {
        try {
            HashSet<E> newSet = (HashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

三、demo示例

1.普通的一个Dog类

/**
 * 这个Dog类,由于没有重写 equals()方法 和 hashCode()方法,
 * 所以,每new一次此类对象,由于地址引用不同,
 * 哪怕name属性的值完全相同,也被视为不同的对象。
 */
public class Dog {

    private String name;

    public Dog(String name) {
        this.name = name;
    }

    //这里重写了toString()方法,为了打印出来时看着方便
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}

2.不普通的Person类

/**
 * 这个Person类,由于重写了 equals() 和 hashCode()方法,
 * 在原本默认的,地址不同,则对象不同的规则上,
 * 我们通过重写上述两个方法,成功的制定了,属于我们自己的新规则。
 */
public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    /**
     * 1.这里重写equals()方法,
     *  规定,当name属性的内容相同时,就认为是同一个对象,return true。
     */
    @Override
    public boolean equals(Object o) {
        //首先比较地址(==在引用数据类型中比较的是地址引用)
        if (this == o) {
            return true;
        }
        //如果类型符合,则进行值比较
        if (o instanceof Person){
            Person p = (Person) o;
            if (this.name.equals(p.name)){
                return true;
            }
        }
        return false;
    }

    
    /**
     * 2.当两个对象的name属性一致时,
     * 让 hashCode()方法 的返回值也一样。
     */
    @Override
    public int hashCode() {
//        return Objects.hash(name);
        return this.name.hashCode();
    }

    
    //这里重写了toString()方法,为了打印出来时看着方便
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

3.测试类

public class Test_main {
    public static void main(String[] args) {

        HashSet<String> hashSet_String = new HashSet<>();
        hashSet_String.add(new String("a1"));
        hashSet_String.add(new String("a1"));
        hashSet_String.add(new String("a1"));
        System.out.println("hashSet_String 中共有 "+hashSet_String.size()+"个对象\n内容为:"+hashSet_String);


        HashSet<Person> hashSet_Person = new HashSet<>();
        hashSet_Person.add(new Person("晴儿"));
        hashSet_Person.add(new Person("晴儿"));
        hashSet_Person.add(new Person("晴儿"));
        System.out.println("\nhashSet_Person 中共有 "+hashSet_Person.size()+"个对象\n内容为:"+hashSet_Person);


        HashSet<Dog>  hashSet_Dog = new HashSet<>();
        hashSet_Dog.add(new Dog("大黄"));
        hashSet_Dog.add(new Dog("大黄"));
        hashSet_Dog.add(new Dog("大黄"));
        System.out.println("\nhashSet_Dog 中共有 "+hashSet_Dog.size()+"个对象\n内容为:"+hashSet_Dog);


    }

}

4.测试结果

在这里插入图片描述由测试结果可知:
1.Java中为我们提供的引用数据类型String类,当我们通过new多次创建内容完全相同的String类型的对象时,由于String类的底层重写了Object类中equals()方法(默认是“==”的比较方式),将原来比较对象地址引用的方式,改成了对象的值比较。所以,当两个String类对象的内容完全相同时,通过重写的equals()方法比较后,返回结果是true,即认为他们是同一个对象。
结果:new了3次内容完全相同的3个对象,最后的判断结果是,只有1个对象。

2.而我们通过class关键字自己定义的Dog类中,由于没有重写 equals() 和 hashCode()方法,那么对象之间是利用Object类中equals()默认的“==”号的方式进行的地址引用的比较。所以,我们每new一次此Dog类的类对象,由于地址引用不同, 哪怕name属性的值完全相同,也会被视为是不同的对象。
结果:new了3次内容完全相同的3个对象,被判断为3个不同的对象。

3.在我们自己定义的Person类中,由于重写了 equals() 和 hashCode()方法。重写hashCode()方法,规定了此对象的hash值的计算方式,也就是间接的规定了此对象在数组中的存放位置。由于又同时重写了equals()方法,实现了对象之间的比较方式,不再采用Object类中equals()默认的比较地址引用的比较方式了,而是调用我们重写后的equals()方法,现在比较的是属性内的具体内容。所以,我们每new一次此Person类的类对象,如果我们在equals()方法内规定的所有属性的属性值都完全相同,就会被视为是同一个对象。
结果:new了3次内容完全相同的3个对象,最后的判断结果是,只有1个对象。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值