zoukankan      html  css  js  c++  java
  • jdk1.8.0_45源码解读——HashSet的实现

    jdk1.8.0_45源码解读——HashSet的实现

    一、HashSet概述

      HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。主要具有以下的特点:

    • 不保证set的迭代顺序,特别是它不保证该顺序恒久不变
    • 有且只允许一个null元素
    • 不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个private static final Object PRESENT = new Object();
    • 非同步的。如果多 个线程同时访问一个哈希set,而其中至少一个线程修改了该 set,那么它必须保持外部同步。这通常是通过对自然封装该set的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来“包装” set。最好在创建时完成这一操作,以防止对该set进行意外的不同步访问:
    Set s = Collections.synchronizedSet(new HashSet(...));
    •    HashSet通过iterator()返回的迭代器是fail-fast的

    二、HashSet源码解析

    相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成。

    1. HashSet类结构

    //通过HashSet实现的接口可知,其支持所有集合操作,能被克隆,支持序列化
    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
        static final long serialVersionUID = -5024744406713321676L;
    
        private transient HashMap<E,Object> map;
    
        //定义一个"虚拟"的static final Object对象作为HashMap的value
        private static final Object PRESENT = new Object();
    
        ......
    }

    HashSet包含了两个重要的成员变量:map, PRESENT。

    (01) map是一个HashMap<E, Object>对象,HashSet是由一个HashMap实例支持的。

    (02) PRESENT是一个static final Object对象,用来作为HashMap中的value值。

     

    2. 构造函数

    HashSet提供了四种方式的构造器,可以构造一个新的空 set,其底层 HashMap实例的默认初始容量是16,加载因子是 0.75,构造一个包含指定collection中的元素的新set,构造一个新的空set,其底层HashMap实例具有指定的初始容量和默认的加载因子(0.75),以及构造一个新的空set,其底层HashMap实例具有指定的初始容量和指定的加载因子。

        //默认的无参构造器,构造一个空的HashSet,实际底层会初始化一个空的HashMap,并使用默认初始容量为16和加载因子0.75。
        public HashSet() {
            map = new HashMap<E, Object>();
        }
    
        //构造一个包含指定collection中的元素的新set。
        //实际底层使用默认的加载因子0.75和足以包含指定collection中所有元素的初始容量来创建一个HashMap。
        public HashSet(Collection<? extends E> c) {
            map = new HashMap<E, Object>(Math.max((int) (c.size()/.75f) + 1, 16));
            addAll(c);  //AbstractCollection.addAll(Collection<? extends E> c)
        }
    
        //以指定的初始容量和加载因子构造一个空的HashSet
        public HashSet(int initialCapacity, float loadFactor) {
            map = new HashMap<E, Object>(initialCapacity, loadFactor);
        }
    
        //以指定的initialCapacity和默认加载因子0.75构造一个空的HashSet
        public HashSet(int initialCapacity) {
            map = new HashMap<E, Object>(initialCapacity);
        }
    
        /**
         * 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合
         * 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持
         * 
         * @param      initialCapacity   初始容量
         * @param      loadFactor        加载因子
         * @param      dummy             标记,用于与其他的构造函数区分(可忽略)
         * @throws     IllegalArgumentException 如果初始容量小于零或加载因子为非正数
         */
        HashSet(int initialCapacity, float loadFactor, boolean dummy) {
            map = new LinkedHashMap<E, Object>(initialCapacity, loadFactor);
        }
    View Code

    3.查找

    HashSet提供了contains(Object o)查看是否包含指定元素的方法,其底层调用的是HashMap.containsKey(Object key)判断是否包含指定key。

        //如果此set包含指定元素,则返回 true
        public boolean contains(Object o) {
            return map.containsKey(o);
        }

    4.添加

    HashSet提供了add(E e)添加元素的方法,其调用的是底层HashMap中的put(K key, V value)方法,首先判断元素(也就是key)是否存在,如果不存在则插入,如果存在则不插入,这样HashSet中就不存在重复值。

       //如果此set中尚未包含指定元素,则添加指定元素
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }

    底层:当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该值确定对象在HashSet中的存储位置。在Hash集合中,不能同时存放两个相等的元素,而判断两个元素相等的标准是两个对象通过equals方法比较相等并且两个对象的HashCode方法返回值也相等。

    注意:对于HashSet中保存的对象,请注意正确重写其equals和hashCode方法,以保证放入的对象的唯一性。

    5.清空与删除

    HashSet提供了remove(Object o)删除元素、clear()清除所有元素的方法。

        //如果指定元素存在于此set中,则将其移除
        public boolean remove(Object o) {
            return map.remove(o)==PRESENT;
        }
    
        //从此set中移除所有元素
        public void clear() {
            map.clear();
        }

    6.其他公开的方法

    size()、isEmpty()、clone()

        //返回此set中的元素的数量
        public int size() {
            return map.size();
        }
    
        //如果此set不包含任何元素,则返回 true
        public boolean isEmpty() {
            return map.isEmpty();
        }
    
        //返回此 HashSet实例的浅表副本
        @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);
            }
        }
    View Code

    支持序列化的写入函数writeObject(java.io.ObjectOutputStream s)和读取函数readObject(java.io.ObjectInputStream s):

        //java.io.Serializable的写入函数,将HashSet的“总的容量,加载因子,实际容量,所有的元素”都写入到输出流中
        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);
        }
    
        // java.io.Serializable的读取函数,将HashSet的“总的容量,加载因子,实际容量,所有的元素”依次读出
        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);
            }
        }
    View Code

    三、HashSet的迭代

     HashSet通过调用HashMap.keySet()返回<key, value>对中的key集以此得到集合的迭代器。

        //返回对此 set中元素进行迭代的迭代器
        public Iterator<E> iterator() {
            return map.keySet().iterator(); //HashMap.keySet()返回<key, value>对中的key集
        }
  • 相关阅读:
    eureka 注册中心(单机版)
    金蝶实际成本培训01
    查看WIN10内核
    金蝶K3 WISE 15.0 GUID
    win10卸载系统自带office365
    金蝶K3wise15.0BOM维护默认只能查看登录账户作为建立人的BOM清单
    阿里云邮箱代收邮件
    金蝶寄售业务流程
    转-商品流通企业代销商品核算方法
    转-ERP待检仓、代管仓、赠品仓
  • 原文地址:https://www.cnblogs.com/CherishFX/p/4790735.html
Copyright © 2011-2022 走看看