zoukankan      html  css  js  c++  java
  • HashSet的故事----Jdk源码解读

    Hash,我们在说HashMap的时候,已经知道Hash是散列,Map是映射了。

    那么Set又是什么呢 ?

    先来看看Set的翻译是什么

    n. [数] 集合;一套;布景;[机] 装置

    这里Set所取的含义是集合。而且是数学概念上的集合。数学概念上的集合有什么特点呢?那就是Set中所有的元素不能重复。所以HashSet的意思就是以散列的形式维持一套不会有重复元素的集合。

    接下来我们看看HashSet是怎么被Jdk实现的吧。(其实逻辑非常简单。)

    类的声明:

    hashSet 继承自AbstractSet,实现了Set类以及克隆接口和可序列化接口。

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

    接口中定义了一个瞬时hashMap变量,这个变量是Set用来存储元素的容器

    private transient HashMap<E,Object> map;

    接着定义一个Object变量,暂且记住这个变量的作用:标记元素是否存在的,具体如何使用,在后边的方法会介绍。

    private static final Object PRESENT = new Object();

    接着是五个构造函数:

    1、无参构造函数

        public HashSet() {
            map = new HashMap<>();
    }

    2、初始化时可以将集合中的元素直接添加到Set中的构造函数

    注意看这个地方计算MAP初始化大小的方式,取出当前集合collection参数的size除以0.75+1,这个数值和16计算,取较大值。

    这样做的好处是,给map赋予一个足够长的大小,这样在给(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )map添加集合collection中元素的时候,map不需要反复rehash。同时如果集合collection中的元素较少时,仍然赋予一个较合适的大小16,为未来添加元素留下足够的空间:

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

    3、以初始容积大小,加载因子作为参数的构造函数

        public HashSet(int initialCapacity, float loadFactor) {
            map = new HashMap<>(initialCapacity, loadFactor);
        }

    4、以初始容积大小作为参数的构造函数

        public HashSet(int initialCapacity) {
            map = new HashMap<>(initialCapacity);
        }

    5、非public的构造函数

    这个构造函数的前两个参数是初始容积和加载因子,第三个参数不会被使用,可以忽略,这个在jkd的注释中有写到。

    同时这个构造函数所使用的map实例时一个linkedHashMap,这与前边的构造函数有所区别。

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
            map = new LinkedHashMap<>(initialCapacity, loadFactor);
        }

    接下来是实例方法:
    返回一个迭代器,这个迭代器实质上返回的是Set中map的key的迭代器

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

    返回set中map的长度,作为Set的长度

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

    判断Set是否为空,其实质其实上返回map的长度是否为0

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

    contains(Object o),Set的核心方法,判断Set中是否存在指定元素o,实质上是判断map中是否存在这个key

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

    add(E e),Set的核心方法,将元素e添加到Set中。

    这个方法的本质,是将e作为key,present变量作为value存入map中。而map.put方法会返回原有的value值,如果是首次添加的话,会返回一个null。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )所以add返回的结果表示当前map是否已经存在元素e。如果存在,则返回false,反之返回true。

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

    remove(Object o),Set的核心方法,移除Set中的o元素。

    如果Set中存在该元素,则返回true。因为map中remove key时,会返回当前key对应的value。

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

    清除Set中的元素

        public void clear() {
            map.clear();
        }

    clone()方法。

    克隆当前Set对象,通过代码可以知道只克隆了当前Set对象,以及map对象。即二者的地址发生了改变,但是对于map中具体的元素,是没有克隆的。

        @SuppressWarnings("unchecked")
        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的方法,但是虚拟机在正反序列化时仍然可以通过反射调用到他们。

    先说序列化writeObject方法。

    首先调用输出流的defaultWriteObject()记录下当前Set可以序列化的值,接着记录map的容积和负载因子以及大小。接着遍历map中的对象,依次记录下key元素(value不用记录,想想这是为什么?)。

    而反序列化则反向的从流中读取数据,然后生成对象。

    由于流是顺序读取的,因此反序列化时的顺序,与序列化中时保持一致的。这里不再过多的介绍正反序列化的内容。(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )

    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);
            }
    
            // Set the capacity according to the size and load factor ensuring that
            // the HashMap is at least 25% full but clamping to maximum capacity.
            capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                    HashMap.MAXIMUM_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);
            }
        }

    最后是spliterator()方法,这个方法是1.8中新添加的。

    该方法返回的是map中对应的一个新的Spliterator实例。这里简单说下Spliterator的作用:spliterator是 split iterator的意思。也就是返回一个迭代器,但是这个迭代器是分割的,将元素分割成若干组,并不像原有的迭代器只能通过单一线程顺序的访问。它可以通过多线程并行的形式,访问容器中的元素,加快元素的访问效率。关于这个类的详细使用,我会在后续的文章中(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )更新。

    public Spliterator<E> spliterator() {
            return new HashMap.KeySpliterator<E,Object>(map, 0, -1, 0, 0);
        }

    通过查看jkd源代码,我们可以发现HashSet的本质,是对map进行了一层封装。使原有的y=f(x)形式可以更好的已数学Set的形式展现出来。

  • 相关阅读:
    PyMySQL TypeError: not enough arguments for format string
    使用python3抓取pinpoint应用信息入库
    JS 异步之 async await
    JS Null 空 判断
    Vue问题汇总
    pymysql DAO简单封装
    py可视化执行过程
    jenkins回滚之groovy动态获取版本号
    容器时间 容器乱码问题
    SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase, Hana]
  • 原文地址:https://www.cnblogs.com/jilodream/p/6120250.html
Copyright © 2011-2022 走看看