Set接口介绍、HashSet源码简要分析

🏠:博客首页: 进击的波吉
📕:今日分享的文章: Set接口介绍、HashSet源码简要分析
💝:希望自己对源码的解读的可以帮助到大家🎈
🌱:Boji 还在努力学JavaSE ,如有疑问、疏漏之处,请多多指点🙏
☀️:自学成长的路上,感谢大家相伴!No hurry , No Pause !💝

1. Set接口

1.1 Set接口基本介绍

  1. 无序,(添加和取出顺序不一致),没有索引(Set没有get方法)
  2. 不允许重复元素,所以最多包含一个null
  3. 取出的顺序虽不是添加的顺序,但顺序其实是固定的, Set底层是数组+链表+红黑树的形式来管理的

1.2 Set接口常用方法

跟List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection一样,详情可以参见这篇博客:

Java集合Collection常用方法详解

1.3 Set接口的遍历方式

同Collection遍历方式一样,因为Set接口是Collection接口的子接口(Collection的方法,Set都可以使用)。

  1. 可以使用迭代器
  2. 增强for
  3. 不能使用索引的方式来获取

2.HashSet的底层结构和源码分析

1.HashSet底层是 HashMap
2.添加一个元素时,先得到hash值-会转化成- > 索引值
3.找到存储表table,看这个索引位置是否已经存放元素 ;
4.如果没有,直接加入;如果有调用equals比较 如果相同就放弃添加,如果不相同,则放到最后 ;
5. 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认到达8),并且table 的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)

代码示例:
在这里插入图片描述

  1. 执行HashSet ,初始化HashSet的底层就是一个HashMap
public HashSet() {
        map = new HashMap<>(); //map: null 
    }
  1. 执行 add(), E 是泛型 ,e 是 java 字符串常量
    PRESENT 是HashSet里面的一个final类型的的static 对象,对象本身没啥意义,主要起到占位的目的,value里面都是存放的这个
    在这里插入图片描述
    在这里插入图片描述

  2. 执行put方法 key:merry , value:PRESENT(共享)
    在这里插入图片描述

  3. HashSet 计算hashcode方法:若 key == null,必定排在HashSet的第一位,若key != null, 将key对应的hashCode值 逻辑右移16位后的值再进行按位异或 生成新的'hashCode' ,可以理解为 为了避免碰撞,让不同的key 生成不同的hashCode值。
    在这里插入图片描述

  4. 执行putVal 方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
        if ((tab = table) == null || (n = tab.length) == 0) //table 是hashMap 中的一个数组,类型是Node[]
            n = (tab = resize()).length; //第一次扩容到 16个空间
        if ((p = tab[i = (n - 1) & hash]) == null) // 计算出该 key 的哈希值 去计算该key存放在哈希表的哪个索引位置,并把这个位置对象赋给 p 
        // 1.如果 p == null ,表示还没有存放元素,就创建一个Node (key, value  )
            tab[i] = newNode(hash, key, value, null); // 同时也存储了哈希值
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold) //threshold 大小为 12 
            resize();
        afterNodeInsertion(evict); //hashmap这个方法可以忽略
        return null;
    }

6.resize()方法

final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table; // null 
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold; // 0
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY) 
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults 只实现这个
            newCap = DEFAULT_INITIAL_CAPACITY; // 初始空间为 16 
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); //临界空间为12 
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //创建新的结点 空间大小为16
        table = newTab; //table的空间大小也为 16  

7.第一个字符串merry ,添加成功,添加 字符串java 方法类似,这里就不再做演示
在这里插入图片描述
8.再次添加merry时,在putVal() 方法中,此时的hash值不为 0,
在这里插入图片描述
9.判断是否需要树化 以及 HashSet 链表添加的流程

 else {
            Node<K,V> e; K k;
            if (p.hash == hash && 
            //如果当前索引位置对应的链表第一个元素和准备添加的key的hash值一样
            //或者满足下面两个条件之一:
               1.准备加入的key 和 p指向的Node 结点的key 是同一个对象	
               2.p 指向的Node 结点 的 equals() 和准备加入的key 比较后相同 
           
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            // 再判断 p 是不是红黑树
            // 如果是红黑树,就调用putTreeVal,来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else { 
            // 如果table 对应的索引位置,已经是一条链表,就使用for 循环
             (1) 依次和该链表的每一个元素比较后,都不相同,则加入到链表的最后
                 注意把元素添加到链表后,立即判断,该链表是否已经达到8个结点 ;
                 就调用treeifyBin()对当前这个链表进行树化(转成红黑树),再进行树化是要进行判断(即table的长度要>= 64) 
            (2)依次和该链表的每一个元素的比较过程中,如果有相同的情况,就直接breakfor (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }

💝学习总结: 在阅读 HashSet的源码过程,相比较 ArrayList的源码,难度的确大了很多。视频是跟随者韩老师的java课程学习的,收益颇丰,为写搞清debug过程,自己大抵看了三遍吧,才算懂得更清楚一些。HashSet的主要就是底层 hashTable 和 每一个表格 是Node类型 最多可以连接 8个节点,当有链表结点数 大于 等于8 时以及 hashTable 大于等于64时,就会进行红黑树化!哈哈哈,大抵就是这样吧!

  • 12
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的波吉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值