Java集合容器系列08-HashSet

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

一、HashSet的介绍

    HashSet是一个依赖于HashMap的Set接口实现,容器的元素存储和操作都是基于内部的一个HashMap实例实现,因为这个原因,它不保证Set中元素的迭代顺序特别是不保证该顺序的恒久不变,允许插入null元素。该类可以为基本的集合操作提供稳定的性能保证,这些基本操作包括add、remove、contains和size,假定哈希函数正确地将元素分布在底层HashMap的槽中,那么对此HashSet进行迭代所需的时间与元素的个数和底层HashMap的槽的个数成正比的,所以迭代性能很重要的话,就不要将初始容量设置得太高(或者负载因子设置得太低)。注意HashSet不是线程安全的容器,如果有多个线程访问该容器,且至少有一个线程对容器做了结构性修改,那么它就必须在外部保证同步,这通常是通过对操作该容器的代码块加锁实现的,如果没有则可以使用Collections.synchronizedSet在包装它作为一个线程安全的容器使用。HashSet的iterator返回的迭代器对象Iterator是fail-fast(快速失败)的,如何理解,即在该迭代器创建之后任何时间对该容器做了结构性修改(除了基于iterator.remve方法删除容器元素之外)都将导致迭代器遍历时抛出ConcurrentModificationException异常,这种快速失败行为无法绝对保证,因此依赖于这个特性编写应用程序是错误的。

二、HashSet的数据结构

public class HashSet
    extends AbstractSet
    implements Set, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    //底层的HashMap实例,这个对象是HashSet的核心
    private transient HashMap map;
    //HashSet的元素其实就是保存在HashMap实例的KeySet集合里,key/value键值对中的value就直接保存它这个固定的对象
    private static final Object PRESENT = new Object();
}

    HashSet继承自AbstractSet类,该类实现了一些集合和Set基本操作方法,实现了Set接口在这里几乎起的类似代码注释的功能,因为AbstractSet本身已经实现了Set接口,通过继承它HashSet也间接实现了Set接口,此外HashSet也实现了Clonable接口支持对象的clone()方法拷贝,实现了java.io.Serializable表明该类也支持序列化

三、HashSet源码分析

1 - 构造方法

    /**
     * 构造函数,底层的HashMap实例默认的初始容量16,负载因子0.75 
     */
    public HashSet() {
        map = new HashMap<>();
    }

    /**
     * 构造函数,在初始化实例时将指定容器的所有元素插入容器中,底层HashMap实例默认负载因子0.75,初始化容量取基于负载 
     * 因子计算的容器容量和默认初始容量的较大值
     */
    public HashSet(Collection c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

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

    /**
     * 构造函数,为底层HashMap指定初始容量,,加载因子默认0.75
     */
    public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

    /**
     * 构造函数,该方法属于包私有的方法仅被LinkedHashSet使用,dummy参数仅仅只是为了和第三个构造方法(底层是HashMap 
     * 实例)做区分
     */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

 

2 - boolean isEmpty()方法 - 判空

    public boolean isEmpty() {
        return map.isEmpty();
    }

    很简单容器的元素都保存在底层的HashMap实例中,所以直接判断底层HashMap实例是否为空。

3 - boolean add(E e) - 添加元素

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

    分析方法源码可知HashSet添加元素实际也是调用底层HashMap的put方法,将元素保存在底层HashMap实例的键值对key/value的key中

 

4 - boolean remove(Object o)

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

    一样也是直接调用底层HashMap的remove方法删除元素o

5 - 其他成员方法

    //返回容器迭代器,无序
    public Iterator iterator() {
        return map.keySet().iterator();
    }

    /**
     * 返回容器保存的元素个数
     */
    public int size() {
        return map.size();
    }

    /**
     * 返回容器是否为空
     */
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /**
     * 如果容器包含对象o返回true否则返回false
     */
    public boolean contains(Object o) {
        return map.containsKey(o);
    }


    public Object clone() {
        try {
            HashSet newSet = (HashSet) super.clone();
            newSet.map = (HashMap) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

    /**
     * 序列化时调用保存对象状态
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
        // Write out any hidden serialization magic
        s.defaultWriteObject();

        // Write out HashMap capacity and load factor
        s.writeInt(map.capacity());
        s.writeFloat(map.loadFactor());

        // Write out size
        s.writeInt(map.size());

        // Write out all elements in the proper order.
        for (E e : map.keySet())
            s.writeObject(e);
    }

    /**
     * 反序列时调用
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read capacity and verify non-negative.
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // Read load factor and verify positive and non NaN.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }

        // Read size and verify non-negative.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                                             size);
        }

        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        SharedSecrets.getJavaOISAccess()
                     .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));

        // Create backing HashMap
        map = (((HashSet)this) instanceof LinkedHashSet ?
               new LinkedHashMap(capacity, loadFactor) :
               new HashMap(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i spliterator() {
        return new HashMap.KeySpliterator(map, 0, -1, 0, 0);
    }

 

6 - HashSet源码总结

    HashSet底层实现依赖于对象内部的一个HashMap实例,容器元素最终是以键值对key的形式保存在底层HashMap实例中,HashSet容器的所有操作实际操作的都是底层的HashMap实例

转载于:https://my.oschina.net/zhangyq1991/blog/1931493

你可能感兴趣的:(Java集合容器系列08-HashSet)