JDK源码util包分析——HashSet源码(8)

HashSet源码分析

HashSet的结构图

在这里插入图片描述

HashSet的概述

无序性(存储元素无序)
唯一性(允许使用null)
本质上,HashSet底层是通过HashMap来保证唯一性
HashSet没有提供get()方法,同HashMap一样,因为Set内部是无序的,所以只能通过迭代的方式获得

HashSet的参数

	// HashSet是通过map(HashMap对象)保存内容的
    private transient HashMap<E,Object> map;

    // 定义一个虚拟的Object PRESENT是向map中插入key-value对应的value
    // 因为HashSet中只需要用到key,而HashMap是key-value键值对;
    // 所以,向map中添加键值对时,键值对的值固定是PRESENT
    private static final Object PRESENT = new Object();

HashSet的构造方法

// 默认构造函数 底层创建一个HashMap
    public HashSet() {
        // 调用HashMap的默认构造函数,创建map
        map = new HashMap<E,Object>();
    }

    // 带集合的构造函数
    public HashSet(Collection<? extends E> c) {
        // 创建map。
        // 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?        
        // 首先,说明(c.size()/.75f) + 1
        //   因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。
        //   当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,
        //   就需要将HashMap的容量翻倍。
        //   所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。
        // 接下来,说明为什么是 16 。
        //   HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;
        //   HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。
        //   所以,这里指定为16是从性能考虑。避免重复计算。
        map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
        // 将集合(c)中的全部元素添加到HashSet中
        addAll(c);
    }

    // 指定HashSet初始容量和加载因子的构造函数
    public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<E,Object>(initialCapacity, loadFactor);
    }

    // 指定HashSet初始容量的构造函数
    public HashSet(int initialCapacity) {
        map = new HashMap<E,Object>(initialCapacity);
    }
	// 注意:这里未用public修饰,主要是给LinkedHashSet使用的
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
    }

HashSet的方法

	// 返回HashSet的迭代器
    public Iterator<E> iterator() {
        // 实际上返回的是HashMap的“key集合的迭代器”
        return map.keySet().iterator();
    }
   //调用HashMap的size()方法返回Entry的数量,得到该Set里元素的个数
    public int size() {
        return map.size();
    }
   //调用HashMap的isEmpty()来判断HaspSet是否为空
   //HashMap为null。对应的HashSet也为空
    public boolean isEmpty() {
        return map.isEmpty();
    }
    //调用HashMap的containsKey判断是否包含指定的key
    //HashSet的所有元素就是通过HashMap的key来保存的
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    // 将元素(e)添加到HashSet中,也就是将元素作为Key放入HashMap中
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    // 删除HashSet中的元素(o),其实是在HashMap中删除了以o为key的Entry
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }
     //清空HashMap的clear方法清空所有Entry
    public void clear() {
        map.clear();
    }

    // 克隆一个HashSet,并返回Object对象
    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();
        }
    }

	// 序列化写出方法
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // 写出非static非transient属性
        s.defaultWriteObject();

        // 写出map的容量和装载因子
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());

        // 写出元素个数
        s.writeInt(map.size());

        // 遍历写出所有元素
        for (E e : map.keySet())
            s.writeObject(e);
    }

    // 序列化读入方法
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // 读入非static非transient属性
        s.defaultReadObject();

        // 读入容量, 并检查不能小于0
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // 读入装载因子, 并检查不能小于等于0或者是NaN(Not a Number)
        // java.lang.Float.NaN = 0.0f / 0.0f;
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }

        // 读入元素个数并检查不能小于0
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                                             size);
        }
        // 根据元素个数重新设置容量
        // 这是为了保证map有足够的容量容纳所有元素, 防止无意义的扩容
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        // 再次检查某些东西, 不重要的代码忽视掉
        SharedSecrets.getJavaOISAccess()
                     .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));

        // 创建map, 检查是不是LinkedHashSet类型
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // 读入所有元素, 并放入map中
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

从上面方法可知:
HashSet其实就是一个对HashMap的封装而已。所有放入HashSet中的集合元素实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。

HashSet内部使用HashMap的key存储元素,以此来保证元素不重复;
HashSet是无序的,因为HashMap的key是无序的;
HashSet中允许有一个null元素,因为HashMap允许key为null;
HashSet是非线程安全的;
HashSet是没有get()方法的;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值