Java进阶(HashMap)——面试时HashMap常见问题解读 结合源码分析

前言
List、Set、HashMap作为Java中常用的集合,需要深入认识其原理和特性。
本篇博客介绍常见的关于Java中HashMap集合的面试问题,结合源码分析题目背后的知识点。
关于List的博客文章如下:
- Java进阶(List)——面试时List常见问题解读 & 结合源码分析
关于的Set的博客文章如下:
- Java进阶(Set)——面试时Set常见问题解读 & 结合源码分析
其他关于HaseMap的文章如下:
- Java学数据结构(3)——树Tree & B树 & 红黑树 & Java标准库中的集合Set与映射Map & 使用多个映射Map的案例
- Java学数据结构(4)——散列表Hash table & 散列函数 & 哈希冲突
目录
- 前言
- 引出
- HashMap底层结构是什么?
- JDK 1.7和1.8 对比
- 源码Node类
- HashMap如何解决Hash碰撞问题?HashMap 何时从单链表,转换为红黑树?
- 当链表节点的数量达到8个时,通过treeify转为红黑树
- HashMap的扩容机制?HashMap的数组何时需要扩容?
- 1.首次添加元素
- 2.初始容量16
- 3.大于16时,双倍扩容
- 总结分析
- HashMap设置长度为11,那么数组的容量为多少?
- 第一个2的幂次方的值
- HashMap 何时从红黑树转换为 单链模式?
- split方法和untreeify方法
- HashMap 为什么在多线程并发使用过程中,容易造成死循环/死锁?
- 如何得到一个线程安全的HashMap集合?
- 总结
引出
1.jdk1.7 HashMap:数组+单向链表;
2.jdk1.8 HashMap:数组+链表(单向)+红黑树;
3.当链表节点的数量达到8个时,通过treeify转为红黑树;
4.首次添加元素,初始容量16,大于16时,双倍扩容;
5.HashMap设置长度,第一个2的幂次方的值;
6.红黑树元素的高位或者低位节点个数<6时,那么就调用untreeify方法来退回链表结构;
7.jdk1.7采用的是头插法,即新来元素在链表起始的位置,而jdk1.8采用尾插法,可以有效的避免在多线程操作中产生死循环;
8.ConcurrentHashMap高并发线程安全;
核心:键值对,KEY不可重复,VALUE可以重复
HashMap底层结构是什么?
JDK 1.7和1.8 对比
jdk1.7 HashMap:数组+单向链表
jdk1.8 HashMap:数组+链表(单向)+红黑树
源码Node类
源码可以看到HashMap内部定义了静态Node类,Node类中成员有

Node<K,V> next;
同样可以看到HashMap内部定义了静态TreeNode类,TreeNode类中成员有

TreeNode<K,V> left;
TreeNode<K,V> right;
可以看出存在红黑树
而TreeNode继承了 LinkedHashMap.Entry,点进查看,可以看到 Entry也继承了 HashMap.Node。所以,TreeNode红黑树是从链表Node中转换过来的

整体图:

HashMap如何解决Hash碰撞问题?HashMap 何时从单链表,转换为红黑树?
- 首先理解什么是hash碰撞问题,HashMap存放的元素的KEY需要经过hash计算得到hash值,而这个hash值可以理解为就是此元素要存放的位置,即数组中的下标;但是如果两个不同元素经过hash计算后,得到的hash值相同时,即两个元素要存放的位置为同一个位置,产生冲突,这种现象就叫做hash碰撞。
- 要想了解HashMap是如何解决hash碰撞的,那我们就需要看看HashMap的put方法的源码中的核心操作

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结构,table为null,则第一次初始化这个table数组的长度为16if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//判断添加的元素的KEY经过hash计算得到的下标位置是否为nullif ((p = tab[i = (n - 1) & hash]) == null)//如果是null,则直接添加元素tab[i] = newNode(hash, key, value, null);//不为null的情况else {Node<K,V> e; K k;//如果key相同,则用新的元素添加到旧元素的位置,后续会将就元素返回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循环遍历链表节点for (int binCount = 0; ; ++binCount) {//如果链表节点next节点为空if ((e = p.next) == null) {//则添加至链表的next节点属性中p.next = newNode(hash, key, value, null);//如果链表节点 >= 7 说明链表存在8个已存的元素节点if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st//转为红黑树方法treeifyBin(tab, hash);break;}//如果KEY相同,匹配其他API 如 putIfAbsent()if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}//存入新值,返回旧值if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}....
当链表节点的数量达到8个时,通过treeify转为红黑树
总结:HashMap是通过3层 if 结构来判断,数组下标位置是否有元素和下标位置的类型是链表还是红黑树,然后通过链表和红黑树来解决hash碰撞的问题,当链表节点>=7时(当链表节点的数量达到8个时),会通过treeify转为红黑树。
HashMap的扩容机制?HashMap的数组何时需要扩容?
1.首次添加元素
HashMap在第一次添加元素时,会进入第一个if结构来初始化数组的长度

2.初始容量16
//添加第一个元素时,会进入这个if结构,table为null,则第一次初始化这个table数组的长度为16if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;

3.大于16时,双倍扩容
此处resize方法就是扩容方法,jdk8中,resize方法除了扩容还增加了初始化的功能,进入此方法我们可以看一下源码

final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;int newCap, newThr = 0;if (oldCap > 0) {//如果当前数组的长度>=最大值(2^30)时,将预值threshold设置为最大值if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}//如果当前数组的长度>=默认的初始长度16,则双倍扩容else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}...
调用resize方法的地方

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) { ...++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
总结分析
- 从上面可以看出,HashMap在完成put元素存储后,会判断++size是否>了阈值,如果是就会去扩容
- 下面这个方法是在,put元素为链表节点,并且要转为红黑树时,会调用该方法,该方法会在一开始就判断是否需要扩容

final void treeifyBin(Node<K,V>[] tab, int hash) {int n, index; Node<K,V> e;if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)resize();
- 判断扩容的核心就是threshold这个值

- 从resize方法中看到,HashMap在扩容时,是之前的双倍扩容
HashMap设置长度为11,那么数组的容量为多少?
第一个2的幂次方的值
指定了长度初始化HashMap时,它会将数组的容量经过一系列算法,设置为大于我们自定义值的第一个2的幂次方的值,
即 设置为11 , 则为2^4=16


static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}
HashMap 何时从红黑树转换为 单链模式?

- HashMap在resize方法执行时,会将元素从旧数组转入新数组,此时如果转移元素为红黑树结构,那么就会调用split方法来分割红黑树方便转移
- split方法内部,在分割时,会生成高位树与低位树两种,此时也会进行判断如果红黑树元素的高位或者低位节点个数<6时,那么就调用untreeify方法来退回链表结构
split方法和untreeify方法

final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {... //lowhead 低位树if (loHead != null) {//在红黑树节点元素往新数组中添加时,会调用split方法来重组这个红黑树//此时会判断,红黑树的节点操作次数是否<6,即low树(低位树的节点数)< 6时,会通过untreeify方法来退化为链表if (lc <= UNTREEIFY_THRESHOLD)tab[index] = loHead.untreeify(map);else {tab[index] = loHead;if (hiHead != null) // (else is already treeified)loHead.treeify(tab);}}//此时会判断,红黑树的节点操作次数是否<6,即high树(高位树的节点数)< 6时,会通过untreeify方法来退化为链表//highhead 高位树 if (hiHead != null) {if (hc <= UNTREEIFY_THRESHOLD)tab[index + bit] = hiHead.untreeify(map);else {tab[index + bit] = hiHead;if (loHead != null)hiHead.treeify(tab);}}
}

HashMap 为什么在多线程并发使用过程中,容易造成死循环/死锁?
图示:

- 产生死循环的核心原因是因为,jdk1.7采用的是头插法,即新来元素在链表起始的位置,而jdk1.8采用尾插法,可以有效的避免在多线程操作中产生以上死循环
- 但是HashMap不是线程安全的,所以在多线程的场景中,虽然不会出现死锁/死循环,但是还是会出现节点丢失的情况,所以在并发的场景中推荐使用ConcurrentHashMap
如何得到一个线程安全的HashMap集合?

ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap<>();
总结
1.jdk1.7 HashMap:数组+单向链表;
2.jdk1.8 HashMap:数组+链表(单向)+红黑树;
3.当链表节点的数量达到8个时,通过treeify转为红黑树;
4.首次添加元素,初始容量16,大于16时,双倍扩容;
5.HashMap设置长度,第一个2的幂次方的值;
6.红黑树元素的高位或者低位节点个数<6时,那么就调用untreeify方法来退回链表结构;
7.jdk1.7采用的是头插法,即新来元素在链表起始的位置,而jdk1.8采用尾插法,可以有效的避免在多线程操作中产生死循环;
8.ConcurrentHashMap高并发线程安全;
相关文章:
Java进阶(HashMap)——面试时HashMap常见问题解读 结合源码分析
前言 List、Set、HashMap作为Java中常用的集合,需要深入认识其原理和特性。 本篇博客介绍常见的关于Java中HashMap集合的面试问题,结合源码分析题目背后的知识点。 关于List的博客文章如下: Java进阶(List)——面试…...
Kotlin 使用@BindingAdapter编译出错
在 Kotlin 中使用 BindingAdapter 注解时,需要确保你的项目正确配置了 Data Binding。 首先,请确保在项目的 build.gradle 文件中启用了 Data Binding: android {// ...dataBinding {enabled true} }接下来,请确保你在正确的地…...
Qt之信号和槽,connect参数分析
connect()方法 Qt进行信号和槽连接,有以下几种方法: static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType Qt::AutoConnection); static QMetaObj…...
Python学习笔记—元组
1、元组定义 元组使用()来定义,元素在()括号内,用逗号隔开 空元组定义,元组名() 注:当元组只有1个元素的时候,需要在元素后面加逗号,…...
【C++项目】高并发内存池第五讲内存回收释放过程介绍
内存回收 1.ThreadCache2.CentralCache3.PageCache 项目源代码:高并发内存池 1.ThreadCache void ThreadCache::Deallocate(void* ptr, size_t size) {assert(ptr);assert(size < MAX_BYTES);//计算在哪号桶中,然后插入进去size_t index SizeClass…...
[毕设记录]@学术工具体验:Sread.ai
我是在查RAG相关的时候,在知乎上面看到了这篇回答:浅谈生成式 AI 技术:检索增强生成 RAG - MarvinZ的文章 - 知乎 https://zhuanlan.zhihu.com/p/659248219 然后在末尾看到了这个 sread.ai 在作者主页看到了他关于这个产品的介绍:…...
uboot - 驱动开发 - 驱动模型
说明 类似于linux,为了规范、统一驱动适配和驱动接口调用,uboot定义了一套驱动模型(Driver Model),简称DM。本文基于:u-boot-2021.10。 优点 为同一类ip的驱动定义了统一的操作接口,DM在软件层面做了一定的抽象。分…...
windows 操作系统命令积累
1. 按 "prt sc" 键 截屏 2. 按 "fn" 键让浏览器进入全屏模式,再次按 "fn" 键让浏览器退出全屏模式( ps:惠普笔记本上是 "fn" "f11" ) 3. ipconfig 查看ip信息 4. 查看指定端口被什么进程占用...
数据结构单链表的实现(C语言)
目录 1.实现的接口和功能2.代码块 1.实现的接口和功能 //打印链表 void SLTPrint(SLTNode** phead); //头插 void PushFont(SLTNode** phead, SLTDataType x); //尾插 void PushBack(SLTNode** phead, SLTDataType x); //头删 void PopFont(SLTNode** phead); //尾删 void Pop…...
Postman的高级使用,傻瓜式学习【下】
目录 前言 1、全局变量、环境变量 1.1、概念: 1.2、如何设置全局变量、环境变量 1.3、获取全局变量、环境变量 1.4、案例1:手动设置变量,请求参数获取 1.5、案例2:代码设置变量,代码获取变量 2、Postman读取外部…...
Qt:关闭对话框,动画实现窗体逐渐缩小到消失
关键技术: 1、使用QPropertyAnimation对象,实现动画效果,逐渐缩小窗体尺寸,以及透明度; 2、在对话框缩小时,要将界面中的控件都隐藏起来,并且将对话框布局的Margin修改成0 代码如下ÿ…...
在Windows上 ciphey安装(详细版)
文章目录 前言 一、不想卸载原有的python版本? 二、安装步骤 1.安装python 2.创建虚拟环境vnev 3.在ciphey的虚拟环境中进行激活 4.安装ciphey 三、参数列表 总结 前言 提示:安装了好几次,但是都没安装成功,我使用了三个电脑p…...
【lesson2】数据库的库操作
文章目录 库操作创建数据库删除数据库字符集和校验规则手动设置字符集和校验集不同字符集和校验集之间的区别修改数据库字符集和校验集备份和恢复数据库 库操作 创建数据库 删除数据库 字符集和校验规则 创建数据库的时候,有两个编码集: 1.数据库编码集…...
Android Studio Giraffe解决gradle reload failed问题
settings.gradle.kts中 pluginManagement {repositories {google()mavenCentral()gradlePluginPortal()} } dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {google()mavenCentral()} } 各增加三行内容&#x…...
刷题笔记day06-哈希表
242.有效的字母异位词 // 思路2:排序后在比较是否相等import ("sort""fmt""io""strings""os" )func isAnagram(s string, t string) bool {s1, s2 : []byte(s), []byte(t)// 从小到大排序sort.Slice(s1, func(i…...
springboot项目中如何实现过滤器鉴权
通常来说鉴权都是写在网关当中,对于单体应用也可以在后台服务中通过一个过滤器实现。其实过程与网关当中的没什么不同,只是在gateway当中目前是基于netty响应式的。过程如下: 一、实现Filter接口 定义自己的过滤器,并且实现Filt…...
【rust/esp32】在idf中使用embedded-graphics控制st7789 LCD屏幕
文章目录 说在前面模块详情准备工作开始编译烧录结果 说在前面 esp32版本:s3运行环境:esp-idf(std)开发环境:wsl2LCD模块:ST7789V2 240*280 LCDgithub地址:这里 模块详情 某宙的esp32s3开发板 某雪的1.69inch LCD模块…...
YOLOv8如何添加注意力模块?
分为两种:有参注意力和无参注意力。 eg: 有参: import torch from torch import nnclass EMA(nn.Module):def __init__(self, channels, factor8):super(EMA, self).__init__()self.groups factorassert channels // self.groups > 0self.softmax …...
用LibreOffice在excel中画折线图
数据表格如下。假设想以x列为横坐标,y1和y2列分别为纵坐标画折线图。 选择插入-》图表: 选择折线图-》点和线,然后点击“下一步”: 选择:列中包含数据序列,然后点击完成(因为图挡住了数据…...
RabbitMQ 链接管理-发布者-消费者
RabbitMQ连接管理器 using RabbitMQ.Client; using System; public class RabbitMQConnectionManager { private readonly IConnectionFactory _connectionFactory; private IConnection _connection; public RabbitMQConnectionManager(string hostName) { …...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
DiscuzX3.5发帖json api
参考文章:PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下,适配我自己的需求 有一个站点存在多个采集站,我想通过主站拿标题,采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...
机器学习的数学基础:线性模型
线性模型 线性模型的基本形式为: f ( x ) ω T x b f\left(\boldsymbol{x}\right)\boldsymbol{\omega}^\text{T}\boldsymbol{x}b f(x)ωTxb 回归问题 利用最小二乘法,得到 ω \boldsymbol{\omega} ω和 b b b的参数估计$ \boldsymbol{\hat{\omega}}…...
云原生安全实战:API网关Envoy的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口,负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...
【深尚想】TPS54618CQRTERQ1汽车级同步降压转换器电源芯片全面解析
1. 元器件定义与技术特点 TPS54618CQRTERQ1 是德州仪器(TI)推出的一款 汽车级同步降压转换器(DC-DC开关稳压器),属于高性能电源管理芯片。核心特性包括: 输入电压范围:2.95V–6V,输…...
