我的jdk源码(十五):HashSet类

一、概述

    HashSet类是常用的Set集合类,底层是采用HashMap来实现的,我们通常利用HashSet类的元素不重复的特性来达到去重的效果,那么跟随源码去一探究竟吧。

二、源码分析

    (1) 类的声明

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

    在类的声明中我们可以看到,HashSet类是继承自AbstractSet类,HashSet类可以复用父类的一些方法,同时HashSet类还是实现了Set接口,以及Cloneable接口和Serializable。

    * AbstractSet继承自AbstractCollection,实现了Set接口,此类并没有重写 AbstractCollection 类中的任何实现(包括add()方法)。它仅仅添加了 equals 和 hashCode 的实现。

    *  实现了Set接口表示本类是一个不包含重复元素的类,并最多包含一个null值。

    *  实现Cloneable接口表示可以调用Object.clone方法返回该对象的浅拷贝。

    *  实现Serializable接口表示可以启用其序列化功能,能通过序列化去传输。

    (2) 成员变量

    //序列化标记ID
    static final long serialVersionUID = -5024744406713321676L;
    //实际存放元素的map,类型是HashMap,所以HashSet是依赖HashMap的
    private transient HashMap<E,Object> map;
    //final修饰的不可改变的空对象
    private static final Object PRESENT = new Object();

    底层通过HashMap来进行数据的存储,通过下面的源码可以得知采用HashMap的key来存储元素,value统一存储一个不可改变的空对象PRESENT,这样既保证HashMap的存储结构,数据的移除操作也是通过HashMap的remove方法进行移除,而HashMap的remove方法会返回被移除的Value值,而把PRESENT设为final就能保证可以通过判断返回的值是否是PRESENT对象就可以判断是否移除成功。

    (3) 构造方法

    //无参构造函数
    public HashSet() {
        //初始化map为一个空的HashMap
        map = new HashMap<>();
    }

    //带容量的构造函数
    public HashSet(int initialCapacity) {
        //初始化一个容量参数为initialCapacity的HashMap,但是最后map的容量不一定是initialCapacity,而是大于等于initialCapacity的最小2次幂
        map = new HashMap<>(initialCapacity);
    }

    //带容量和负载因子的构造函数
    public HashSet(int initialCapacity, float loadFactor) {
        //在上一个方法基础上,多了设置负载因子这一步
        map = new HashMap<>(initialCapacity, loadFactor);
    }

    //带容量和负载因子以及标记dummy的构造函数,但是没有public修饰。dummy标记是用来区分HashSet(int initialCapacity, float loadFactor)方法
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }
    
    //传入一个集合c的构造函数
    public HashSet(Collection<? extends E> c) {
        //创建一个足够大的map,容量计算规则是取(c.size()/.75f)+1和16取较大的值作为initialCapacity,但是这也不是最终的容量,最终的容量还是大于initialCapacity的最小2次幂
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        //添加集合c中的所有元素到map中
        addAll(c);
    }

    (4) add()方法

    //添加一个元素
    public boolean add(E e) {
        //调用HashMap的put方法,因为HashMap的Key不能重复,重复时会把添加的元素直接返回,成功则返回null
        return map.put(e, PRESENT)==null;
    }

    HashSet类为什么能去重呢?它的add()方法告诉了我们答案,HashSet在调用set()方法的时候,是把添加的元素作为了HashMap的key,而value就是上面final修饰的空对象PRESENT。我看到这儿我惊了, HashSet利用HashMap的key不能重复的机制,因为key重复就覆盖value,以此来达到去重的效果,我只能说绝了!

    (5) 其余核心方法

    //调用HashMap的clear方法来清空元素
    public void clear() {
        map.clear();
    }

    //调用HashMap的containsKey来判断是否包含元素o
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    //通过调用HashMap的isEmpty方法来判断集合是否为空
     public boolean isEmpty() {
        return map.isEmpty();
    }

    //返回迭代器
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

    //调用HashMap的remove方法来移除元素
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

    //HashMap的大小就是集合的大小
    public int size() {
        return map.size();
    }

    HashSet类的其他方法也比较简单,都是直接调用HashMap的方法,如果你对HashMap还不熟,那我推荐你看一下《我的jdk源码(十三):HashMap 一磕到底,追根溯源!》。

三、总结

    HashSet类还是有几点比较重要的知识点,罗列如下:

    * HashSet底层依赖HashMap,所以也是线程不安全的。

    * HashSet底层依赖HashMap,所以支持动态扩容,但也因此和HashMap一样,无法保证元素的有序性。

    * 我们可以利用HashSet的元素不重复特性来达到为集合去重的效果。

    更多精彩内容,敬请扫描下方二维码,关注我的微信公众号【Java觉浅】,获取第一时间更新哦!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java觉浅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值