zoukankan      html  css  js  c++  java
  • HashSet源码解析笔记

    HashSet是基于HashMap实现的。HashSet底层采用HashMap来保存元素,因此HashSet底层其实比较简单。

    HashSet是Set接口典型实现,它按照Hash算法来存储集合中的元素,具有很好的存取和查找性能。主要具有以下特点:

    • 不保证set的迭代顺序
    • HashSet不是同步的,如果多个线程同时访问一个HashSet,要通过代码来保证其同步
    • 集合元素值可以是null

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

    HashSet中每一能存储元素的槽位通常称为“桶”,如果有多个元素的hashCode相同,但是通过equals方法比较返回false,就需要在一个桶上存放多个元素。

    来看源码:

      

      public class HashSet<E>

        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
        static final long serialVersionUID = -5024744406713321676L;
    
        // HashSet是通过map(HashMap对象)保存内容的
        private transient HashMap<E,Object> map;
    
        // 定义一个虚拟的Object PRESENT是向map中插入key-value对应的value
        // 因为HashSet中只需要用到key,而HashMap是key-value键值对;
        // 所以,向map中添加键值对时,键值对的值固定是PRESENT
        private static final Object PRESENT = new Object();
    
        // 默认构造函数 底层创建一个HashMap
        public HashSet() {
            // 调用HashMap的默认构造函数,创建map
            map = new HashMap<E,Object>();
        }
    
        // 带集合的构造函数
        public HashSet(Collection<? extends E> c) {
            // 创建map。
            // 为什么要调用Math.max((int) (c.size()/.75f) + 1, 16),从 (c.size()/.75f) + 1 和 16 中选择一个比较大的树呢?        
            // 首先,说明(c.size()/.75f) + 1
            //   因为从HashMap的效率(时间成本和空间成本)考虑,HashMap的加载因子是0.75。
            //   当HashMap的“阈值”(阈值=HashMap总的大小*加载因子) < “HashMap实际大小”时,
            //   就需要将HashMap的容量翻倍。
            //   所以,(c.size()/.75f) + 1 计算出来的正好是总的空间大小。
            // 接下来,说明为什么是 16 。
            //   HashMap的总的大小,必须是2的指数倍。若创建HashMap时,指定的大小不是2的指数倍;
            //   HashMap的构造函数中也会重新计算,找出比“指定大小”大的最小的2的指数倍的数。
            //   所以,这里指定为16是从性能考虑。避免重复计算。
            map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16));
            // 将集合(c)中的全部元素添加到HashSet中
            addAll(c);
        }
    
        // 指定HashSet初始容量和加载因子的构造函数
        public HashSet(int initialCapacity, float loadFactor) {
            map = new HashMap<E,Object>(initialCapacity, loadFactor);
        }
    
        // 指定HashSet初始容量的构造函数
        public HashSet(int initialCapacity) {
            map = new HashMap<E,Object>(initialCapacity);
        }
    
        HashSet(int initialCapacity, float loadFactor, boolean dummy) {
            map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);
        }
    
        // 返回HashSet的迭代器
        public Iterator<E> iterator() {
            // 实际上返回的是HashMap的“key集合的迭代器”
            return map.keySet().iterator();
        }
       //调用HashMap的size()方法返回Entry的数量,得到该Set里元素的个数
        public int size() {
            return map.size();
        }
       //调用HashMap的isEmpty()来判断HaspSet是否为空
       //HashMap为null。对应的HashSet也为空
        public boolean isEmpty() {
            return map.isEmpty();
        }
        //调用HashMap的containsKey判断是否包含指定的key
        //HashSet的所有元素就是通过HashMap的key来保存的
        public boolean contains(Object o) {
            return map.containsKey(o);
        }
    
        // 将元素(e)添加到HashSet中,也就是将元素作为Key放入HashMap中
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
    
        // 删除HashSet中的元素(o),其实是在HashMap中删除了以o为key的Entry
        public boolean remove(Object o) {
            return map.remove(o)==PRESENT;
        }
         //清空HashMap的clear方法清空所有Entry
        public void clear() {
            map.clear();
        }
    
        // 克隆一个HashSet,并返回Object对象
        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();
            }
        }
    
        // 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 (Iterator i=map.keySet().iterator(); i.hasNext(); )
                s.writeObject(i.next());
        }
    
    
        // 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 in HashMap capacity and load factor and create backing HashMap
            int capacity = s.readInt();
            float loadFactor = s.readFloat();
            map = (((HashSet)this) instanceof LinkedHashSet ?
                   new LinkedHashMap<E,Object>(capacity, loadFactor) :
                   new HashMap<E,Object>(capacity, loadFactor));
    
            // Read in size
            int size = s.readInt();
    
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                E e = (E) s.readObject();
                map.put(e, PRESENT);
            }
        }
    }

    从上述HashSet源代码可以看出,它其实就是一个对HashMap的封装而已。所有放入HashSet中的集合元素实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。

    HashSet的绝大部分方法都是通过调用HashMap的方法来实现的,因此HashSet和HashMap两个集合在实现本质上是相同的。

    根据HashMap的一个特性: 将一个key-value对放入HashMap中时,首先根据key的hashCode()返回值决定该Entry的存储位置,如果两个key的hash值相同,那么它们的存储位置相同。如果这个两个key的equalus比较返回true。那么新添加的Entry的value会覆盖原来的Entry的value,key不会覆盖。因此,如果向HashSet中添加一个已经存在的元素,新添加的集合元素不会覆盖原来已有的集合元素。

    在java的集合中,判断两个对象是否相等的规则是:

    首先,判断两个对象的hashCode是否相等 如果不相等,认为两个对象也不相等 如果相等,则判断两个对象用equals运算是否相等 如果不相等,认为两个对象也不相等 如果相等,认为两个对象相等 
    我们在equals方法中需要向下转型,效率很低,所以先判断hashCode方法可以提高效率。
    equals()相等的两个对象,hashcode()一定相等; equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashcode()有可能相等。(我的理解是由于哈希码在生成的时候产生冲突造成的)。 
    反 过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

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

    HashCode:

    1、HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的

    2、如果两个对象equals相等,那么这两个对象的HashCode一定也相同

    3、如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写 

    4、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置

     

  • 相关阅读:
    Vue 服务器端渲染(一)
    vue笔记 介绍及安装 一
    Node.js 学习笔记 (一) 安装配置
    Java开发中的23种设计模式详解(转)
    spring-boot整合ehcache实现缓存机制
    STM32流水灯
    SD卡封转及管脚说明
    随笔分类
    函数的设计之我见
    让灵魂追得上我们疲惫的身体
  • 原文地址:https://www.cnblogs.com/yzf666/p/6433692.html
Copyright © 2011-2022 走看看