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)
上一篇 2021年11月12日 下午2:00
下一篇 2021年11月12日 下午3:00


相关推荐

  • 解决SecureCRT 中文乱码

    解决SecureCRT 中文乱码在linux服务器上搭建solr,用的是SecureCRT 连接linux服务器,发现不能输入中文,配置文件中的中文也是乱码;先以为是SecureCRT工具编码的问题,把编码改成utf-8之后发现还是有乱码;最后才发现其实还有一个地方没有改配置,那就是字体必须是中文字体,而且字符集得是支持中文的字符集如gb2312:还有就是这里不能选择带@的中文字体,不然字体就是躺着的

    2022年7月17日
    19
  • 导弹防御系统(dfs+最长上升子序列)

    导弹防御系统(dfs+最长上升子序列)原题连接/为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 4 的导弹。给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。输入格式输入包含多组测试用例。对于每个测试用例,第一行包含整数 n,表示来袭导弹数量。第二行包含 n 个不同的整数,表示每个导弹的高度。当输入测试用例

    2022年8月8日
    12
  • java图书销售系统,基于jsp的图书销售管理系统-JavaEE实现图书销售管理系统 – java项目源码…[通俗易懂]

    java图书销售系统,基于jsp的图书销售管理系统-JavaEE实现图书销售管理系统 – java项目源码…[通俗易懂]基于jsp+servlet+pojo+mysql实现一个javaee/javaweb的图书销售管理系统,该项目可用各类java课程设计大作业中,图书销售管理系统的系统架构分为前后台两部分,最终实现在线上进行图书销售管理系统各项功能,实现了诸如用户管理,登录注册,权限管理等功能,并实现对各类图书销售管理系统相关的实体进行管理。该图书销售管理系统为一个采用mvc设计模式进行开发B/S架构项…

    2022年5月1日
    55
  • onpropertychange属性

          当一个input标签的value通过键盘改变后失去焦点的时候,我们可以用onchange捕获事件,IE和FireFox都有效。而通过JavaScript来改变value的时候,onchange无法捕获事件,这时候我们可以用onpropertychange来捕获onpropertychange对于JavaScript和键盘对value的改变都有效。遗憾的是,onpropertychange事件只在IE中有效,FireFox中无效。尽FireFox的oninput可以来弥补(不必失去焦点),但是也仅

    2022年4月6日
    51
  • 二叉树的前序遍历,中序遍历,后序遍历(Java实现)

    二叉树的前序遍历,中序遍历,后序遍历(Java实现)1 前序遍历 nbsp nbsp nbsp nbsp 前序遍历 DLR lchild data rchild 是二叉树遍历的一种 也叫做先根遍历 先序遍历 前序周游 可记做根左右 前序遍历首先访问根结点然后遍历左子树 最后遍历右子树 前序遍历首先访问根结点然后遍历左子树 最后遍历右子树 在遍历左 右子树时 仍然先访问根结点 然后遍历左子树 最后遍历右子树 若二叉树为空则结束返回 否则 1 访问根结点 2 前序遍历左子树 3

    2026年3月20日
    2
  • matlab中ewma实现,ewma 移动平均模型

    matlab中ewma实现,ewma 移动平均模型动平均(WMA)制图AL的计算方ME控R法J本文基于马尔可夫链的ME.WMA控制图AL计算的数学模型,用MaaR采tb平台,该模l对学术界和实际应用……在采样数据标准化处理的基础上,应用马尔可夫链进一步研究了多元指数移动平均(MEWMA)控制图中ARL计算的数学模型,采用Matlab实现了算法,就算法的收敛性及计算……3【摘要】针对设备异常…

    2025年8月9日
    6

发表回复

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

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