当前位置: 首页 > news >正文

面试之CurrentHashMap的底层原理

首先回答HashMap的底层原理?

HashMap是数组+链表组成。数字组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。要将key 存储到(put)HashMap中,key类型实现必须计算hashcode方法,默认这个方法是对象的地址。接着还必须要覆盖对应的equals方法。如果对于插入的操作的来说,那么对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部。对于查找的来说话,就需要遍历 链表,然后key的equals方法去逐一对比查找。但是对应的key可以为空。所以,HashMap对应的链表越少,性能才越好。

 如何解决冲突?

1:链式寻址法,这是一种常见的方法,简单理解就是把存在Hash冲突的key,以单向链表来进行存储。

2:开放定址法也称线性探测法,就是从发生冲突的那个位置开始,按照一定次序从Hash表找到一个空闲位置然后把发生冲突的元素存入到这个位置,而在java中,ThreadLocal就用到了线性探测法来解决Hash冲突

3、再Hash法,就是通过某个Hash函数计算的key,存在冲突的时候,再用另外一个Hash函数对这个可以进行Hash,一直运算,直到不再产生冲突为止,这种方式会增加计算的一个时间,性能上呢会有一些影响

HashMap在JDK1.8版本中是通过链式寻址法以及红黑树来解决Hash冲突的问题,其中红黑树是为了优化Hash表的链表过长导致时间复杂度增加的问题,当链表长度大于等于8并且Hash表的容量大于64的时候,再向链表添加元素,就会触发链表向红黑树的一个转化。(红黑树 是一种自平衡的二叉搜索树

hashCode方法的作用:它返回的就是根据对象的内存地址换算出的一个值。这样一来,当
集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理
位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如
果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相
同就散列其它的地址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

HashTable的底层原理?

hashtable是通过数组与链表来储存数据,但是它与hashmap不同的是它的key不能为空同时其为线程安全的。虽然hashmap是线程安全的不过其保证线程安全的手段低效,它只是简单的对每个方法加上synchronized(悲观锁)相当于就是对底层的数组加上一把大锁。这种方式出现锁冲突的概率非常大,因为不管是读还是写都需要去竞争同一把锁所以其效率低下。

再次回答CurrentHashMap的底层原理?

ConcurrentHashMap与HashMap等的区别 ?

其实可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树。

1.数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
2.保证线程安全机制:JDK1.7采用segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8采用CAS+Synchronized保证线程安全。
3.锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。
4.链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
5.查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。

CurrentHashMap是如何保证线程安全。

这里的链表长度 如果大于8 会自动 转换为 红黑树。这里的数据结构和jdk1.8的HashMap数据结构大致相同。只是在HashMap的基础上增加线程安全的操作。

final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException(); // 键或值为空,抛出异常// 键的hash值经过计算获得hash值,这里的 hash 计算多了一步 & HASH_BITS,HASH_BITS 是 0x7fffffff,该步是为了消除最高位上的负符号 hash的负在ConcurrentHashMap中有特殊意义表示在扩容或者是树结点int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;) { // 无限循环Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0) // 表为空或者表的长度为0// 初始化表tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 表不为空并且表的长度大于0,并且该桶不为空if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null))) // 比较并且交换值,如tab的第i项为空则用新生成的node替换break;                   // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED) // 该结点的hash值为MOVED// 进行结点的转移(在扩容的过程中)tab = helpTransfer(tab, f);else {V oldVal = null;synchronized (f) { // 加锁同步if (tabAt(tab, i) == f) { // 找到table表下标为i的结点if (fh >= 0) { // 该table表中该结点的hash值大于0// binCount赋值为1binCount = 1;for (Node<K,V> e = f;; ++binCount) { // 无限循环K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) { // 结点的hash值相等并且key也相等// 保存该结点的val值oldVal = e.val;if (!onlyIfAbsent) // 进行判断// 将指定的value保存至结点,即进行了结点值的更新e.val = value;break;}// 保存当前结点Node<K,V> pred = e;if ((e = e.next) == null) { // 当前结点的下一个结点为空,即为最后一个结点// 新生一个结点并且赋值给next域pred.next = new Node<K,V>(hash, key,value, null);// 退出循环break;}}}else if (f instanceof TreeBin) { // 结点为红黑树结点类型Node<K,V> p;// binCount赋值为2binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) { // 将hash、key、value放入红黑树// 保存结点的valoldVal = p.val;if (!onlyIfAbsent) // 判断// 赋值结点value值p.val = value;}}}}if (binCount != 0) { // binCount不为0if (binCount >= TREEIFY_THRESHOLD) // 如果binCount大于等于转化为红黑树的阈值// 进行转化treeifyBin(tab, i);if (oldVal != null) // 旧值不为空// 返回旧值return oldVal;break;}}}// 增加binCount的数量addCount(1L, binCount);return null;
}

JDK1.8的CurrentHashMap与 JDK1.7的CurrentHashMap的对比:

1:抛弃了JDK1.7中的原有的Segment中分段锁。而是采用了CAS+synchronized来保证并发性。

2:将 JDK 1.7 中存放数据的 HashEntry 改为 Node,但作用是相同的。

我们看下对应的put方法思想: (主要是6步法:思想:通过cas将新生成node节点插入table,若对应的node节点已经存在,则将sychronized锁住哈希桶进行更新或者插入尾巴的下一个节点)[CAS补充一下思想:CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。]

     1:如果key=null或者value =null,那么对应的对应的异常

     2:计算此key的hash值,然后就进入自旋,自旋确保数据可以插入。首先判断对应的table表为空或者长度为0,若为空,则初始化table表。

    3:根据 key 的 hash 值取出 table 表中的结点元素,若取出的结点为空(该桶为空),则使用CAS将对应的key,value,hash放入对应的桶中。(就是上面图片中对于table的长度计算,对应还有没有空,有空的话,就生成对应的Node节点。)

  4:若该结点的的 hash 值为 MOVED(-1),则对该桶中的结点进行转移。节点转移就是扩容的过程中。

  5: 对于桶中的(第一个节点)进行加锁,然后进行遍历。,根据桶中的hash值和key值 与本次添加key的值和根据key计算的hash 进行逐一比对。若出现相等的情况则 根据对应的value值进行更新。否则就生成一个节点则赋值给最后一个节点的下一个节点。

6:若binCount的值 达到一个 转换为红黑树的阈值。则转换为 红黑树。

ConcurrentHashmap 不支持 key 或者 value 为 null 的原因?

1:ConcurrentHashmap 和 Hashtable 都是支持并发的,当通过 get(k) 获取对应的 value 时,如果获取到的是 null 时,无法判断是 put(k,v) 的时候 value 为 null,还是这个 key 从来没有做过映射。
2:HashMap 是非并发的,可以通过 contains(key) 来做这个判断。
3:支持并发的 Map 在调用 m.contains(key) 和 m.get(key) 时,m 可能已经发生了更改。
因此 ConcurrentHashmap 和 Hashtable 都不支持 key 或者 value 为 null。
 

相关文章:

面试之CurrentHashMap的底层原理

首先回答HashMap的底层原理? HashMap是数组链表组成。数字组是HashMap的主体&#xff0c;链表则是主要为了解决哈希冲突而存在的。要将key 存储到&#xff08;put&#xff09;HashMap中&#xff0c;key类型实现必须计算hashcode方法&#xff0c;默认这个方法是对象的地址。接…...

Error in onLoad hook: “ReferenceError: plus is not defined“ found in

项目场景&#xff1a; 项目背景如下所示&#xff1a; 使用 HBuilder X 开发 项目&#xff0c; 调整页面时&#xff0c;直接运行到 浏览器查看页面设置效果&#xff0c;导致控制台出现下述报错信息 例如&#xff1a; 问题描述 遇到的问题如下所示&#xff1a; APP 中接收数据…...

ansible自动化运维(二)剧本、角色编写实战

&#x1f618;作者简介&#xff1a;一名运维工作人员。 &#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。 &#x1f64f;创作不易&#xff0c;动动小…...

【Spring框架】@Resource注入以及与@Autowired的区别

目录 使用Resource设置name的方式来重命名注入的对象区别 使用Resource设置name的方式来重命名注入的对象 package com;import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.spr…...

FTP服务器的搭建和配置上传脚本

文章目录 前言一、配置本地用户可上传权限ftp服务器1、用户登录ftp 二、配置FTP上传脚本文件1.脚本代码如下 补充知识 前言 vsftpd&#xff08;Very Secure FTP Daemon&#xff09;是一个在 Linux/Unix 系统上运行的一款开源免费的 FTP 服务器软件。vsftpd 支持支持 匿名用户、…...

Ubuntu22.04上部署Lua开发环境

需求背景 想在Ubuntu22.04上搭建一下Lua的开发环境&#xff0c;其实步骤比较简单的&#xff0c;此文章也适用于Ubuntu主机环境搭建Lua,如果想在在Ubuntu内部署一个容器&#xff0c;然后在容器内搭建Lua的环境&#xff0c;可以先参考容器的创建过程 ubuntu22.04上如何创建有pri…...

React的hooks---自定义hooks

通过自定义 Hook&#xff0c;可以将组件逻辑提取到可重用的函数中&#xff0c;在 Hook 特性之前&#xff0c;React 中有两种流行的方式来共享组件之间的状态逻辑&#xff1a;render props和高阶组件&#xff0c;但此类解决方案会导致组件树的层级冗余等问题。而自定义 Hook 的使…...

Asp.Net 使用Log4Net (基础版)

Asp.Net 使用Log4Net (基础版) 1. 创建项目 创建ASP.NET Web Forms项目 在Visual Studio中创建一个新的ASP.NET Web Forms项目。命名为"Log4NetDemo"。 2.安装Log4Net包 打开NuGet包管理器控制台&#xff0c;并运行以下命令来安装Log4Net&#xff1a; mathemati…...

STM32 互补PWM 带死区 HAL

1、设置PWM波频率100KHz&#xff0c;占空比50%&#xff0c;死区时间1us 2、 while 循环之前启动PWM HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); //启动TIM1_CH1 PWM输出 HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1);//启动TIM1_CH1N PWM输出 3、死区计算 DT_time…...

20230721在WIN10下安装openssl并解密AES-128加密的ts视频切片

20230721在WIN10下安装openssl并解密AES-128加密的ts视频切片 2023/7/21 22:58 1、前言&#xff1a; AES-128加密的ts视频切片【第一个】&#xff0c;打开有时间限制的&#xff01; https://app1ce7glfm1187.h5.xiaoeknow.com/v2/course/alive/l_64af6130e4b03e4b54da1681?typ…...

使用Python实现产品图片自动化处理

大家好&#xff0c;在当今的数字化时代&#xff0c;产品图片在电子商务和市场营销中发挥着至关重要的作用。然而&#xff0c;为在线平台准备产品图片可能是一项耗时的任务&#xff0c;本文将分享一个Python脚本&#xff0c;用于自动化产品图片的图像处理工作流程。通过使用Pyth…...

在CSDN学Golang云原生(git)

一&#xff0c;git的工作流程 Golang的Git工作流程与其他语言的Git工作流程类似&#xff0c;通常包括以下步骤&#xff1a; 创建分支&#xff1a;在本地代码库中创建一个新的分支&#xff0c;该分支用于开发新功能或修复错误。编写代码&#xff1a;在创建的分支上进行编码&am…...

QT多线程编程基础

文章目录 前言一、线程&#xff0c;进程 介绍二、创建线程三、终止线程总结 前言 一、线程&#xff0c;进程 介绍 线程&#xff1a; 是操作系统中独立运行的最小单位。每个线程都有自己的执行路径、程序计数器、堆栈和一组寄存器。线程共享进程的资源&#xff0c;如内存和文件…...

TRT4-trt-integrate - 3 使用onnxruntime进行onnx的模型推理过程

前言&#xff1a; onnx是microsoft开发的一个中间格式&#xff0c;而onnxruntime简称ort是microsoft为onnx开发的推理引擎。允许使用onnx作为输入进行直接推理得到结果。 py接口的推理过程&#xff1a; main函数&#xff1a; if __name__ "__main__":session onn…...

layui+drogon完成文件上传(简例)

layui界面加入按钮、文本框、进度条&#xff1a; <div class"layui-row"><button type"button" class"layui-btn" id"file_upload_control">文件上传</button><input type"file" id"files_input…...

高精度地图服务引擎项目

技术栈&#xff1a;使用vue3TypeScriptElement PlusPiniaaxios 项目描述&#xff1a;高精度地图服务引擎项目&#xff0c;提供轻量化处理3D瓦片切片分布式处理分发服务的一站式解决方案 工作内容&#xff1a;1、项目60%已上的页面开发 2、部分模块的功能实现&#xff0c; 3、封…...

PyTorch使用Transformer进行机器翻译

文章目录 简介数据集环境要求实验代码实验结果参考来源 简介 本文使用PyTorch自带的transformer层进行机器翻译&#xff1a;从德语翻译为英语。从零开始实现Transformer请参阅PyTorch从零开始实现Transformer&#xff0c;以便于获得对Transfomer更深的理解。 数据集 Multi30…...

LoadRunner使用教程

1. LoadRunner简介 LoadRunner是一款广泛使用的性能测试工具 可以对各种应用程序进行性能测试&#xff0c;包括Web应用程序、移动应用程序、企业级应用程序等。它提供了一个综合的性能测试解决方案&#xff0c;包括测试计划设计、脚本录制、测试执行、结果分析和报告生成等功…...

Zia和ChatGPT如何协同工作?

有没有集成ChatGPT的CRM系统推荐&#xff1f;Zoho CRM已经正式与ChatGPT集成。下面我们将从使用场景、使用价值和使用范围等方面切入讲述CRMAI的应用和作用。 Zia和ChatGPT如何协同工作&#xff1f; Zia和ChatGPT是不同的人工智能模型&#xff0c;在CRM中呈现出共生的关系。 …...

【位操作】——获取整数变量最低位为 1 的位置

获取整数变量最低位为 1 的位置 #define BIT_LOW_BIT(y) (((y)&BIT(0)) ? 0 : (((y)&BIT(1)) ? 1 : (((y)&BIT(2)) ? 2 : (((y)&BIT(3)) ? 3 : \(((y)&BIT(4)) ? 4 : (((y)&BIT(5)) ? 5 : (((y)&BIT(6)) ? 6 : (((y)&…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

c++第七天 继承与派生2

这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分&#xff1a;派生类构造函数与析构函数 当创建一个派生类对象时&#xff0c;基类成员是如何初始化的&#xff1f; 1.当派生类对象创建的时候&#xff0c;基类成员的初始化顺序 …...

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...

02.运算符

目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&&#xff1a;逻辑与 ||&#xff1a;逻辑或 &#xff01;&#xff1a;逻辑非 短路求值 位运算符 按位与&&#xff1a; 按位或 | 按位取反~ …...