Java集合(9) - HashSet源码解析

HashSet

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

 

      HashSet是Set接口的实现,按照hash算法来存储集合中的元素,具有很好的存取和查找性能。

      HashSet是无序,元素不可重复的,但是集合中的元素可以为null,只会有一个null,同时具备有Fail-Fast机制,即非线程安全的。

 

属性:从这里可以看到HashSet的元素是存储在HashMap的,就是利用HashMap的Key的特性,这也是为什么在此系列中,Map是先解析。

/**

 * 实际存储,内部元素存储到HashMap

 */

private transient HashMap<E,Object> map;

 

/**

 * value占位符:用来存储到Mapvalue

 */

private static final Object PRESENT = new Object();

 

序列化:

      可以看到HashSet和ArrayList一样,实现了Serializable接口,但是源码中的内置数组是用transient来修饰的,标识不需要序列化。

      所以HashSet在序列化和反序列化时,会调用writeObject/readObject()方法进行手工序列化,将HashSet的元素(即0~size-1)和容量大小写出,这样做的好处也是一样的,只保存/传输有实际意义的元素,最大限度的节约了存储、传输和处理的开销。

/**

 * 写出

 * @param s

 */

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

}

 

/**

 * 流读取

 * @param s

 */

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

    }

 

    //读取负载因子,不能为0NaN

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

 

    //再次检查,HashMap中构建hash桶数组是在第一个元素被添加时候才构建的,所以在构建之前检查

    //调用HashMap.tableSizeFor来计算实际分配的大小

    //检查Map.Entry[]类,因为是最接近公共类型实际创建的内容

    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实例的默认初始化容量是16,加载因子是0.75

 */

public HashSet() {

    map = new HashMap<>();

}

/**

 * 包含指定collection中的元素的新set

 */

public HashSet(Collectionextends E> c) {

    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));

    addAll(c);

}

/**

 * 新的空set,其底层是HashMap,所以指定初始化容量和加载因子

 * @param initialCapacity 初始化容量

 * @param loadFactor

 */

public HashSet(int initialCapacity, float loadFactor) {

    map = new HashMap<>(initialCapacity, loadFactor);

}

/**

 * set,底层是HashMap,默认的加载因子是0.75

 * @param initialCapacity 指定容量

 */

public HashSet(int initialCapacity) {

    map = new HashMap<>(initialCapacity);

}

 

/**

 * 这个构造器的修饰符是defualt,这个构造器主要是给子类LinkedHashSet用的

 * @param initialCapacity

 * @param loadFactor

 * @param dummy 这个是用于LinkedList

 */

HashSet(int initialCapacity, float loadFactor, boolean dummy) {

    map = new LinkedHashMap<>(initialCapacity, loadFactor);

}

 

其他常用方法:

/**

 * 迭代器

 */

public Iterator<E> iterator() {

    return map.keySet().iterator();

}

 

/**

 * 集合元素个数

 */

public int size() {

    return map.size();

}

 

/**

 * 集合是否为空

 */

public boolean isEmpty() {

    return map.isEmpty();

}

 

/**

 * 是否包含某个元素

 */

public boolean contains(Object o) {

    return map.containsKey(o);

}

 

/**

 * 新增元素

 */

public boolean add(E e) {

    return map.put(e, PRESENT)==null;

}

 

/**

 * 删除元素

 */

public boolean remove(Object o) {

    return map.remove(o)==PRESENT;

}

 

/**

 * 清空

 */

public void clear() {

    map.clear();

}

 

/**

 * 可分割迭代器,主要用于多线程并行迭代处理使用的

 */

public Spliterator<E> spliterator() {

    return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);

}

     

      以上的其他操作元素的方法中,会发现都是调用HashMap的方法,所以底层就是HashMap,如迭代器就是调用HashMap.key.iterator,而这个就是在AbstractMap源码解读时所提到的keySet()方法内定义的迭代器,进而也可以推断出HashSet就是数组+单链表/红黑树。

      注:在Set接口中也没有发现有get()方法,在HashSet也发现没有get()方法,但是有迭代方法,这说明了要访问Set元素,只能通过迭代器来遍历元素,不能随机访问,因为元素存储时就是hashCode计算位置。

你可能感兴趣的:(Java)