java集合之set集合的实现类HashSet源码分析

一、HashSet底层实现的数据结构为什么?简单介绍

     HashSet的底层实现是HashMap,而HashMap的底层实现为散链表(数组+链表)

     因此在学习HashSet的源码是我们必须线先掌握对HashMap的底层实现。(HashMap的底层实现网址:https://mp.csdn.net/postedit/79756598)

    List集合和Set集合分别为有序可重复、无序不可重复,那么我们的set集合为什么不可以存放重复元素?我们将在文章最后给出简答。

二、对HashSet的主要变量源码分析

       //序列化版本号

    static final long serialVersionUID = -5024744406713321676L;

    

   //HashSet底层存储数据的容器;通过HashMap实现

    private transient HashMap<E,Object> map;

    

   //定义Object对象用于一会在add值时候作为Value值存入map中

    // Dummy value to associate with an Object in the backing Map

    private static final Object PRESENT = new Object();

三、HashSet之构造函数

   //无参数构造,对map变量赋值

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

    }

// initialCapacity初始化数组长度大小;loadFactor加载因子

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

    }

// initialCapacity初始化数组长度大小;loadFactor加载因子取HashMap底层的构造函数的默认值0.75

     public HashSet(int initialCapacity) {

        map = new HashMap<>(initialCapacity);

    }

// 有参数构造,通过给定的集合创建map的大小,如果传入(集合大小/0.75f强制装换为int类型+1)和 16取最大值

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

    }  

三、对HashSet中主要方法讲解

      1、向集合中添加元素

       public boolean add(E e) {

          // 调用map的put方法往容器里面添加数据;Map中存放key===>value,value就用定义的Object对象作为value值存入

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

      }

     2、删除集合中的元素

         public boolean remove(Object o) {

              return map.remove(o)==PRESENT;

          }

    3、清空HashSet集合中的元素

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

            }

   4、返回HashSet集合容器的大小

      public int size() {

         //调用HashMap的size()方法

        return map.size();

    }

 面试题:HashSet中为什么不能存放重复的数据?

           因为在HashSet中数据的底层存储为HashMap实现的;在HashMap添加元素方法add中;当往HashMap中添加元素时除了存在增长策略存在,还对数据的存储通过了计算:

      1、首先通过Hash算法确定数据的存储在数组中的位置

      2、对通过Hash算法计算的到的数组位置进行是否存在数据判断;不存在就将数据放入该数组位置

      3、如果该数组位置存在数据,那么就遍历该位置上的链式结构,如果存在相同的key值就将原来的value值覆盖掉

      4、如果不存在就在链式结构的最后追加该数据信息

   上述问题中我们讲到了Hash算法,那么它是怎么进行计算的尼?

    //jdk1.7的hash(Object o)算法

         final int hash(Object k) {

         //初始值为0

        int h = hashSeed;

        if (0 != h && k instanceof String) {

            return sun.misc.Hashing.stringHash32((String) k);

      c  }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by

        // constant multiples at each bit position have a bounded

        // number of collisions (approximately 8 at default load factor).

        //右移动 后 再 异或操作

        h ^= (h >>> 20) ^ (h >>> 12);

        return h ^ (h >>> 7) ^ (h >>> 4);

    }

  //在jdk1.7中确定数据存放在数组中的位置索引,其中h为上面计算的hash函数的返回值

 static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : “length must be a non-zero power of 2”;
        return h & (length-1);
    }

   //jdk1.8的hash(Object o)算法

   static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    通过保存对象进行初步计算如果 Object==null,则返回0,否则得到调用对象的hashCode()^(对象的hashCode>>>16)

   在jdk1.7中也有对传入对象为null的判断,如果为null,那么得到的hash值为0,一定是放在数组的起始位置。


    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/ao_dream/article/details/79744315
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞