HashSet的add()方法源码解析(jdk1.8)

HashSet的add()方法源码解析(jdk1.8)

HashSet

  1. 实现了Set接口
  2. 实际上是HashMap
  3. 可以存null,但只能有一个
  4. 不保证元素是有序的,取决于hash后,在确定索引结果

add源码

//核心操作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;
    // 步骤①:tab为空则创建 
    // table未初始化或者长度为0,进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 步骤②:计算index,并对null做处理  
    // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    // 桶中已经存在元素
    else {
        Node<K,V> e; K k;
        // 步骤③:节点key存在,直接覆盖value 
        // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
                // 将第一个元素赋值给e,用e来记录
                e = p;
        // 步骤④:判断该链为红黑树 
        // hash值不相等,即key不相等;为红黑树结点
        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;
                }
                // 判断链表中结点的key值与插入的元素的key值是否相等
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 相等,跳出循环
                    break;
                // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
                p = e;
            }
        }
        // 表示在桶中找到key值、hash值与插入元素相等的结点
        if (e != null) { 
            // 记录e的value
            V oldValue = e.value;
            // onlyIfAbsent为false或者旧值为null
            if (!onlyIfAbsent || oldValue == null)
                //用新值替换旧值
                e.value = value;
            // 访问后回调
            afterNodeAccess(e);
            // 返回旧值
            return oldValue;
        }
    }
    // 结构性修改
    ++modCount;
    // 步骤⑥:超过最大容量 就扩容 
    // 实际大小大于阈值则扩容
    if (++size > threshold)
        resize();
    // 插入后回调
    afterNodeInsertion(evict);
    return null;
}

解释:add流程

  1. 使用构造器时,执行新建一个HashMap对象

  2. 执行add方法

  3. 执行map的put方法

    1. 计算出hash值为:key.hash = (h = k.hashCode()) ^ (h >>> 16);(hashCode与自身无符号右移16位做异或

    因为通常声明map集合时不会指定大小,或者初始化的时候就创建一个容量很大的map对象,所以这个通过容量大小与key值进行hash的算法在开始的时候只会对低位进行计算,虽然容量的2进制高位一开始都是0,但是key的2进制高位通常是有值的,因此先在hash方法中将key的hashCode右移16位在与自身异或,使得高位也可以参与hash,更大程度上减少了碰撞率。

  4. 执行putVal方法、

    1. 判断table是否为null(为null则扩容到16,阈值为0.75*容量 = 12)
      1. 使用hash进行高效取余计算出应该存在table表中的那个索引位置
        1. 索引位为null,直接存入时,新建一个Node对象,传入三个参数,hash值(为了下次添加时比较hash值),key值(添加的值),value值(一个哨兵变量,占位用,为了set使用hashMap,每个key的value都一样(PRESENT)),next(null)
        2. 不为null,产生冲突
          1. 判断是否属于同一个对象,或者equals判断相等(将e赋值为当前下标对应的Node)
          2. 判读是否属于红黑树,(属于则 将p强转为TreeNode,调用putTreeVal,将e赋值)
          3. 将当前下表的链表进行for循环
            1. 如果链表中有节点是和将要添加的对象属于同一对象,或者equals判断相等,则break;
            2. 如果循环到了链表尾,则进行添加
              1. 判断结点数量是否达到阈值(8),到8则转化为红黑树
                1. 转换之前,判断table数组大小是否小于64或等于null,小于则扩容
                2. 转化红黑树
      2. 判断e是否为null,不为空返回旧值(添加失败)
    2. 判断++size是否大于阈值,大于则进行扩容
    3. 返回null(添加成功)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/115178.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 两个正序数组 找中位数_有一组已安排好序的数组

    两个正序数组 找中位数_有一组已安排好序的数组原题连接给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。示例 1:输入:nums1 = [1,3], nums2 = [2]输出:2.00000解释:合并数组 = [1,2,3] ,中位数 2示例 2:输入:nums1 = [1,2], nums2 = [3,4]输出:2.50000解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5示例 3:输入:nums1 = [0,

    2022年8月9日
    3
  • iOS创建PDF文件

    iOS创建PDF文件

    2021年8月25日
    100
  • loadrunner视频教程百度云_loadrunner使用教程图文

    loadrunner视频教程百度云_loadrunner使用教程图文播客视频,java虚拟用户http://www.boobooke.com/v/bbk1900/LR动态链接库小布老师视频:测试工具概述,兼LoadRunner介绍-1-4http://www.boobooke.com/v/bbk1046http://www.boobooke.com/v/bbk1047http://www.boobooke.com/v/

    2022年10月14日
    3
  • matlab经典版_魔方矩阵matlab

    matlab经典版_魔方矩阵matlabfs=44100;dt=1/fs;T16=0.125;t16=[0:dt:T16];[tempk]=size(t16);t4=linspace(0,4*T16,4*k);t8=linspace(0,2*T16,2*k);[tempi]=size(t4);[tempj]=size(t8);mod4=(t4.^4).*exp(-30*(t4.^0.5));mo…

    2022年9月22日
    2
  • Redis布隆过滤器原理与实践

    Redis布隆过滤器原理与实践背景在高并发请求时,业务数据一般会对数据进行缓存,提高系统并发量,因为磁盘IO和网络IO相对于内存IO的成百上千倍的性能劣势。做个简单计算,如果我们需要某个数据,该数据从数据库磁盘读出来需要0.1s,从交换机传过来需要0.05s,那么每个请求完成最少0.15s(当然,事实上磁盘和网络IO也没有这么慢,这里只是举例),该数据库服务器每秒只能响应67个请求;而如果该数据存在于本机内存里,读出来只需要10us,那么每秒钟能够响应100,000个请求。通过将高频使用的数据存在离cpu更近的位置,以减少数据传

    2022年10月7日
    4
  • 手机修改域名服务器,手机修改域名服务器ip地址

    手机修改域名服务器,手机修改域名服务器ip地址手机修改域名服务器ip地址内容精选换一换安装依赖时,使用pip3.7.5installxxx命令安装相关软件时提示无法连接网络,且提示“Couldnotfindaversionthatsatisfiestherequirementxxx”,提示信息如下所示。没有配置pip源。配置pip源,配置方法如下:如果提示目录不存在,则执行如下命令创建:在.pip目录安装依赖时,使用p…

    2022年6月16日
    31

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号