Java集合容器系列08-HashSet

1、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异常,这种快速失败行为没法绝对保证,所以依赖于这个特性编写应用程序是错误的。java

2、HashSet的数据结构

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

    //底层的HashMap实例,这个对象是HashSet的核心
    private transient HashMap<E,Object> 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代表该类也支持序列化安全

3、HashSet源码分析

1 - 构造方法

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

    /**
     * 构造函数,在初始化实例时将指定容器的全部元素插入容器中,底层HashMap实例默认负载因子0.75,初始化容量取基于负载 
     * 因子计算的容器容量和默认初始容量的较大值
     */
    public HashSet(Collection<? extends E> 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<E> 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<E> newSet = (HashSet<E>) super.clone();
            newSet.map = (HashMap<E, Object>) 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<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

    /**
     * 返回一个延迟绑定的迭代器
     */
    public Spliterator<E> spliterator() {
        return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
    }

 

6 - HashSet源码总结

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

相关文章
相关标签/搜索