java源码阅读HashSet

Posted OUYM

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java源码阅读HashSet相关的知识,希望对你有一定的参考价值。

1类签名与注解

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

此类实现Set接口,由哈希表(实际为HashMap实例)支持。 对集合的迭代次序不作任何保证。特别是,它不能保证顺序在一段时间内保持不变(HashMap的扩容重hash)。 这个类允许null元素。

请注意,此实现不同步。 如果多个线程并发访问哈希集,并且至少有一个线程修改该集合,那么它必须在外部进行同步。 这通常通过在自然地封装集合的一些对象上进行同步来实现。 如果没有这样的对象存在,那么该集合应该使用Collections.synchronizedSet方法“包装”。 这最好在创建时完成,以防止对该集合的意外不同步访问:

 Set s = Collections.synchronizedSet(new HashSet(...)); 

该类iterator方法返回的迭代器是故障快速的。此机制在HashMap一节中有详细讲述。

 

2属性

static final long serialVersionUID = -5024744406713321676L;

private transient HashMap<E,Object> map;

// Map中的一个虚拟值
private static final Object PRESENT = new Object();

HashSet是通过HashMap实现的,所以内部持有map的引用,Set的值对应着Map中的key,但是每次map的插入需要是<key,value>的键值对,所以就有了虚拟的value对象,就是PRESENT。

 

3构造方法

//1默认
public HashSet() {
        map = new HashMap<>();
    }

//2通过集合构造
public HashSet(Collection<? extends E> c) {
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }

//3指定HashMap的初始化容量和负载因子
public HashSet(int initialCapacity, float loadFactor) {
        map = new HashMap<>(initialCapacity, loadFactor);
    }

//4指定HashMap的初始化容量
public HashSet(int initialCapacity) {
        map = new HashMap<>(initialCapacity);
    }

//5指定初始化容量和负载因子,内部通过LinkedHashMap实现
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

注意:构造方法5的dummy参数其实没有特别的意义,唯一作用是通过多一个参数来区别构造方法3(方法重载)。

 

4常用方法

(1)add

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

(2)remove

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

(3)contains

public boolean contains(Object o) {
        return map.containsKey(o);
    }

(4)其他常用

public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

public int size() {
        return map.size();
    }

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

HashSet的常用方法都是通过调用HashMap的方法实现的。

 

5其他

(1)clone

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);
        }
    }

(2)序列化与反序列化

//序列化
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();

        // 读capacity(检查非负).
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " + capacity);
        }

        // 读负载因子(不能为null).
        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
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        // Constructing the backing map will lazily create an array when the first element is
        // added, so check it before construction. Call HashMap.tableSizeFor to compute the
        // actual allocation size. Check Map.Entry[].class since it\'s the nearest public type to
        // what is actually created.

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

        // 构造HashMap对象
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // 往HashMap中添加键值对
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

(3)equals

HashSet类中没有实现equls方法,而是从父类AbstractSet中继承过来的。AbstractSet中equls实现如下:

public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Set))
            return false;
        Collection<?> c = (Collection<?>) o;
        if (c.size() != size())
            return false;
        try {
            return containsAll(c);
        } catch (ClassCastException unused)   {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }
    }

public boolean containsAll(Collection<?> c) {
        for (Object e : c)
            if (!contains(e))
                return false;
        return true;
    }

public boolean contains(Object o) {
        Iterator<E> it = iterator();
        if (o==null) {
            while (it.hasNext())
                if (it.next()==null)
                    return true;
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return true;
        }
        return false;
    }

equals首先判断是否引用同1个对象,若是则返回true。

否则,判断是否都是Set类型的,若不是则返回false。

若是,判断是否都有相等的size,若不是则返回false。

若是,则调用containsAll是否包含所有元素,若是则返回true,否则返回false。

 

以上是关于java源码阅读HashSet的主要内容,如果未能解决你的问题,请参考以下文章

源码阅读(27):Java中主要的Set结构——LinkedHashSetTreeSet等结构

JDK源码阅读:最简单的HashSet源码分析

Java中HashSet使用源码演示

JavaSE_坚持读源码_HashSet对象_Java1.7

如何进行 Java 代码阅读分析?

jdk源码阅读笔记之java集合框架(基础篇)