Java中Hashset存储原理底层深挖

news2024/11/17 1:32:10

上课老师讲了Hashset的添加元素方法,感觉不甚准确,于是下课扒一扒底层源码,这一看,霍!

原来如此。现在小丁来捋一遍他的存储原理。

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

可以看到PRESENT是一个private static final修饰的object对象

private static final Object PRESENT = new Object();

首先它返回了一个map的put方法,并判断它是不是添加成功,添加成功返回true,反之返回false

继续

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

接着他又返回了一个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)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            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)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

不急咱一点一点拆着看

首先搞清楚前面定义的Node<K,V>[] tab这玩意是啥,有node节点

static class Node<K,V> implements Map.Entry<K,V>

 静态内部类再看一眼方法,是链表节点

 先解释一下参数是什么意思

hash: 键的哈希值,用于确定键值对在数组中的位置。
key: 键,可以是任意类型的对象。
value: 值,可以是任意类型的对象。
onlyIfAbsent: 一个布尔值,表示是否只在键不存在时才插入值。

先看前面

首先判断数组是否为空或长度为零,如果是则调用resize方法。

resize方法是用来扩容的方法

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

      

然后根据哈希值和数组长度计算出键值对在数组中的索引i,并获取该位置的节点p。

如果p为空,则说明该位置没有发生哈希冲突,直接创建一个新节点并放入数组中。

  if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

那么剩下的情况就是该位置已经有元素了,发生了哈希冲突了,用else来嵌套剩下的情况

1.如果p的哈希值和键与传入的参数相同,则说明找到了相同的键,直接将p赋值给e。

e相当于一个中间变量

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;

2.如果p的节点是treenode节点就把他放进去

看眼treenode是什么东西,发现是是一个静态内部类,并且里面有boolean red这个属性,是判断此节点是红还是黑的,原来这个东西是一个红黑树节点

 else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);

3.如果都不是的话,就只有一种情况了,下面是个链表,要顺着往下遍历看看有没有相同元素

于是再用一个else来嵌套剩下的情况

3.1

使用一个循环遍历链表,每次将p的下一个节点赋值给e,并判断e是否为空或者与传入的参数相同。

TREEIFY_THRESHOLD这个玩意是类中的一个常量值为8

说明链表在长度为8时转换为红黑树

for (int binCount = 0; ; ++binCount) {
    if ((e = p.next) == null) {//如果e为空,则说明到达了链表尾部,没有找到相同的键
        p.next = newNode(hash, key, value, null);//,创建一个新节点并插入到链表尾部,
        if (binCount >= TREEIFY_THRESHOLD - 1) // 并判断是否需要将链表转化为红黑树。
            treeifyBin(tab, hash);
        break;
    }
    if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
        break;//如果e不为空且与传入的参数相同,则说明找到了相同的键,跳出循环。
    p = e;//每次循环后将e赋值给p,以便下一次循环使用
}                

3.2

如果e不为空,则说明存在相同的键,需要更新其对应的值。

然后返回该节点的值不空

所以put返回的为旧节点的值非空,故add返回false显示添加失败

if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
}

3.3

同理put返回的为旧节点的值为空,故add返回true显示添加成功

 ++modCount;//增加修改计数器modCount和元素个数size
 if (++size > threshold)//判断是否需要扩容数组。
     resize();
 afterNodeInsertion(evict);//用afterNodeInsertion方法处理后续逻辑,并返回null。
 return null;

总结一下:

HashSet添加元素会调用HashMap的添加方法值作为key,一个空对象作为value

而HashMap用hash计算的出元素的hash值并放在对应的数组索引上,如果hash冲突了就在下面挂一个链表。如果找到了同一个元素就把新加入的元素值赋给旧元素。

当这条链表长度大于8时 ,这条链表自动转换成红黑树存储

大概长这个样子:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1025755.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTTPS 证书生成脚本详细讲解

前言 HTTPS证书的作用是用于保障网站的安全性。在HTTPS协议中&#xff0c;通过使用证书来实现客户端与服务器之间的认证和数据加密&#xff0c;防止中间人攻击、信息泄漏等安全问题的发生。https证书也就是SSL证书&#xff0c;我们首先要确定好需要 https 安全连接的域名&…

IMX6ULL移植篇-Linux内核源码目录分析一

一. Linux内核源码目录 之前文章对 Linux内核源码的文件做了大体的了解&#xff0c;如下&#xff1a; IMX6ULL移植篇-Linux内核源码文件表_凌肖战的博客-CSDN博客 本文具体说明 Linux内核源码的一些重要文件含义。 二. Linux内核源码中重要文件分析 1. arch 目录 这个目录…

Spring的后处理器

Spring后处理器 Spring后处理器是Spring对外开放的重要拓展点&#xff08;让我们可以用添加自己的逻辑&#xff09;&#xff0c;允许我们介入到Bean的整个实例化流程中来&#xff0c;以达到动态注册BeanDefinition&#xff08;向BeanDefitionMap中添加BeanDefition对象的过程&…

【视觉SLAM入门】8. 回环检测,词袋模型,字典,感知,召回,机器学习

"见人细过 掩匿盖覆” 1. 意义2. 做法2.1 词袋模型和字典2.1.2 感知偏差和感知变异2.1.2 词袋2.1.3 字典 2.2 匹配(相似度)计算 3. 提升 前言&#xff1a; 前端提取数据&#xff0c;后端优化数据&#xff0c;但误差会累计&#xff0c;需要回环检测构建全局一致的地图&…

数据结构与算法(C语言版)P5---栈

1、栈 1.1、栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。__进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。__栈中的数据元素遵守__后进先出&#xff08;先进后出&#xff09;__LIFO&#xf…

【C++STL基础入门】list改、查操作

文章目录 前言一、list查操作1.1 迭代器循环1.2 for_each函数 二、list改操作2.1 迭代器修改2.2 assign函数2.3 运算符 总结 前言 C标准模板库&#xff08;STL&#xff09;是C语言中非常重要的部分&#xff0c;它提供了一组通用的模板类和函数&#xff0c;用于处理常见的数据结…

利用C++开发一个迷你的英文单词录入和测试小程序-源码

接上一篇&#xff0c;有了数据库的查询&#xff0c;再把小测试的功能给补足&#xff0c;小程序的结构就出来了。 备注&#xff1a;enable_if 有更优秀的concept C 20替代品&#xff0c;C11 里面提到的any&#xff0c;variant&#xff0c;再C17 已经被纳入了标准库。这里完全可…

iOS加固保护技术:保护你的iOS应用免受恶意篡改

目录 转载&#xff1a;开始使用ipaguard 前言 下载ipa代码混淆保护工具 获取ipaguard登录码 代码混淆 文件混淆 IPA重签名与安装测试 转载&#xff1a;开始使用ipaguard 前言 iOS加固保护是直接针对ios ipa二进制文件的保护技术&#xff0c;可以对iOS APP中的可执行文件…

机器学习(17)---支持向量机(SVM)

支持向量机 一、概述1.1 介绍1.2 工作原理1.3 三层理解 二、sklearn.svm.SVC2.1 查看数据集2.2 contour函数2.3 画决策边界&#xff1a;制作网格2.4 建模画图 三、非线性情况推广3.1 查看数据集3.2 线性画图3.3 为非线性数据增加维度并绘制3D图像 四、核函数 一、概述 1.1 介绍…

记一次 mysql 数据库定时备份

环境&#xff1a;Centos 7.9 数据库&#xff1a;mysql 8.0.30 需求&#xff1a;生产环境 mysql 数据&#xff08;约670MB&#xff09;备份。其中存在大字段、longblob字段 参考博客&#xff1a;Linux环境下使用crontab实现mysql定时备份 - 知乎 一、数据库备份 1. 备份脚本。创…

Python项目Flask ipv6双栈支持改造

一、背景 Flask 是一个微型的(轻量)使用Python 语言开发的 WSGI Web 框架(一组库和模块),基于Werkzeug WSGI工具箱/库和Jinja2 模板引擎,当然,Python的WEB框架还有:Django、Tornado、Webpy,这暂且不提。 Flask使用BSD授权。 Flask也被称为microframework(微框架),F…

RFID技术在工业智能制造生产线中的应用

随着自动化和信息化的快速发展&#xff0c;工业智能制造成为制造业的重要趋势&#xff0c;在制造商的生产线上&#xff0c;准确获取和管理工艺流程等各个环节的信息至关重要&#xff0c;作为物联网感知层的核心组成部分&#xff0c;RFID技术以其非接触式、无感知的特点&#xf…

隔山打牛:金融大崩溃

当2004-2006年美联储主席格林斯潘在任期的末尾一鼓作气把联邦利率从1%拉高到5%&#xff0c;然后把美联储主席的位子交给继任者伯南克的时候&#xff0c;没有人意识到接下来将要发生何等巨变。 图&#xff1a;美国联邦利率 伯南克把利率稳定在5.3%附近的高位一年左右时间&#x…

【ArcGIS】基本概念-矢量空间分析

栅格数据与矢量数据 1.1 栅格数据 栅格图是一个规则的阵列&#xff0c;包含着一定数量的像元或者栅格 常用的栅格图格式有&#xff1a;tif&#xff0c;png&#xff0c;jpeg/jpg等 1.2 矢量数据 矢量图是由一组描述点、线、面&#xff0c;以及它们的色彩、位置的数据&#x…

无涯教程-JavaScript - AVEDEV函数

描述 AVEDEV函数返回数据点与其平均值的绝对偏差的平均值。 AVEDEV是数据集中变异性的量度。 语法 AVEDEV (number1, [number2] ...)争论 Argument描述Required/OptionalNumber11 to 255 arguments for which you want the average of the absolute deviations.Requirednum…

趴趴雅思作文修改

前言 在网上试了下趴趴雅思作文修改的服务&#xff0c;淘宝上直接就可以购买&#xff0c;首次有优惠&#xff0c;之后还是挺贵的。 用了下感觉&#xff0c;有用还是有用的&#xff0c;但是挺贵的&#xff0c;一次40&#xff0c;不值。他给我作文的评分是7分&#xff0c;应该给…

fineReport11.0.4版本新建数据链接

需要以下几步&#xff1a; 1.设计器和服务器都需要安装对应数据库的驱动&#xff08;已安装就跳过&#xff09; 对应驱动可以在官网下载&#xff0c;百度搜下有教程 2.服务器没有驱动需要上传驱动 2.1 服务器上传驱动文件&#xff0c;需要修改finedb中的fine_conf_entity表…

Mobpush上线跨时区推送功能,助力中国开发者应用出海

近年来随着国内移动应用存量市场饱和&#xff0c;国内移动应用厂商转向”移动出海“&#xff0c;把握国际市场中存在结构性发展机会&#xff0c;提升中国品牌移动应用的知名度和影响力。根据公开资料显示&#xff0c;中国应用开发者中有79.1%计划出海&#xff0c;其中43%的开发…

极光笔记 | 大语言模型插件

在人工智能领域&#xff0c;大语言模型&#xff08;LLMs&#xff09;是根据预训练数据集进行”学习“&#xff0c;获取可以拟合结果的参数&#xff0c;虽然随着参数的增加&#xff0c;模型的功能也会随之增强。但无论专业领域的小模型&#xff0c;还是当下最火、效果最好的大模…

分享一下微信公众号怎么实现积分商城功能

微信公众号作为一种社交媒体平台&#xff0c;可以帮助商家与消费者进行互动和沟通。除了实现微信拼团活动外&#xff0c;微信公众号还可以实现积分商城功能&#xff0c;提高消费者的参与度和忠诚度。本文将介绍如何在微信公众号实现积分商城功能。 一、了解积分商城 积分商城是…