jdk7.0源码解析HashSet底层实现原理

jdk7.0源码解析HashSet底层实现原理


一、HashSet的概述

HashSet是Set接口的实现类,用于存储多个元素,存储特点是无序、无下标,元素不可以重复,

同时允许null作为元素;而且线程不安全,因为内部方法为非同步方法。

二、HashSet的存储结构

对于HashSet而言,底层是基于HashMap实现的;HashSet数据的存储是利用HashMap存储对

应的数据,相关的操作也是调用HashMap的方法来实现,所以HashSet的底层实现相对比较简单。

三、HashSet内部实现原理机制(源码解析)

  1. HashSet的基本元素

    public class HashSet<E> extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {
          
          
        static final long serialVersionUID = -5024744406713321676L;
        
    	// 底层定义一个HashMap,用于存储HashSet的元素
        private transient HashMap<E,Object> map;
    
        // 定义一个虚拟的Object对象,作为HashMap的value,被static final修饰 
        private static final Object PRESENT = new Object();
    
  2. HashSet中的构造方法:

    /*
    	无参数的构造器,其实底层默认创建一个空的HashMap对象,
    	并使用默认的初始容量为16,加载因子为0.75
    */
    public HashSet() {
          
          
    	map = new HashMap<>();
    }
    /*
    	创建一个包含 Collection中的元素HashSet对象,
    	底层默认创建一个HashMap,容量为足以存储Collection中的元素,
    	同时采用默认的加载因子 0.75
    */
    public HashSet(Collection<? extends E> c) {
          
          
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
    /*
    	利用指定的容量和加载因子创建一个新的HashSet对象,
    	传递的参数作为HashMap的参数:代表容量和加载因子
    */
    public HashSet(int initialCapacity, float loadFactor) {
          
          
        map = new HashMap<>(initialCapacity, loadFactor);
    }
    /*
    	利用指定的参数创建一个新的HashSet对象,
    	实际底层是以此参数创建一个HashMap对象,同时采用默认的
    	加载因子
    */
    public HashSet(int initialCapacity) {
          
          
        map = new HashMap<>(initialCapacity);
    }
    
  3. HashSet中常见方法的实现:

    /*
    	往 HashSt中添加一个元素:添加成功-true;不成功-false
    	
    	实际底层调用 HashMap中的put方法,将要添加的元素作为键,
    	用字段中定义的虚拟Object对象作为值,进行存储,当存储的
    	key在HashMap中已经存在(哈希值相同,同时调用equals方法
    	返回值为true)时,key不改变,但是会用新值覆盖旧值,但是
    	每一次添加时,value都是用的同一个虚拟 Object对象。
    	
    	所以:向HashSet中添加元素时,如果此元素已经存在(判断
    	依据:哈希码值相同,同时equals方法的返回值为true),集合
    	中的元素不会发生改变,这也满足了Set的不重复性。
    	
    	但是需要注意的就是,判别元素在HashSet中是否存在,在put
    	方法中调用了 hashCode方法 和 equals方法进行判断,所以
    	为了保证HashSet的元素不重复性,需要做到以下两点:
    	(1) 覆盖 hashCode方法
    	    原则:内容相同的对象返回相同的哈希码值,为了提高效率
    	         内容不同的对象,尽可能的返回不同的哈希码值
    	(2) 覆盖equals方法
    	    原则:内容相同的对象,结果返回true
    */
    public boolean add(E e) {
          
          
        return map.put(e, PRESENT)==null;
    }
    /*
    	返回此Set中的元素个数:
    	实际底层调用了Map中的size方法返回Entry的数量,返回值
    	代表Set集合中元素的个数
    */
    public int size() {
          
          
        return map.size();
    }
    /*
    	判断HashMap中是否为空:
    	实际底层调用HashMap的isEmpty方法判断是否为空
    */
    public boolean isEmpty() {
          
          
        return map.isEmpty();
    }
    /*
    	判断HashSet中是否包含某一个元素:
    	底层实际调用HashMap的containsKey方法,判断
    	HashMap中是否包含此键
    */
    public boolean contains(Object o) {
          
          
        return map.containsKey(o);
    }
    /*
    	从HashSet中删除元素:
    	实际底层调用的是 HashMap的 remove方法
    */
    public boolean remove(Object o) {
          
          
        return map.remove(o)==PRESENT;
    }
    /*
    	将HashMap中的元素进行清除:
    	实际底层调用 Map中的remove方法
    */
    public void clear() {
          
          
        map.clear();
    }
    

    应用层解析:根据put方法的分析,当HashMap中存储键值对时,仅仅考虑key,完全不考虑value,只是根据key来计算并决定每一个键值对(Entry)存储的位置。如果保证键的不重复性,需要让内容相同的键有一个相同的存储位置,这样会让for循环的过程中if条件成立(此过程中需要调用equals方法),则才能让重复的键对应的值,新值替换旧值;但是如果每存储一个键值对时,都获取相同的存储下标,这样数组的同一个下标对应的链表就会很长,并且每一次存储都需要调用equals方法具体比较键的内容是否相同,则会降低存储的效率,所以为了提高效率,尽可能满足内容不同的键给定一个不同的存储下标,这样可以尽量让HashMap中的元素分布均匀即每一个位置上尽可能一个元素。

    所以如果自定类型的元素往HashSet中存储(作为HashMap的键)时,需要覆盖hashCode方法和equals方法:

    (1) 覆盖hashCode方法的原则:

    ​ a. 必须保证内容相同的元素返回相同的哈希码值

    ​ b. 为了提高效率,尽可能做到内容不同的元素返回不同的哈希码值

    (2) equals方法:内容相同的对象返回true。

四、相关总结

HashSet底层本质为HashMap,,所以先理解HashMap的底层实现原理,再来学习HashSet的

底层实现原理。

猜你喜欢

转载自blog.csdn.net/Java_lover_zpark/article/details/102969219