Java程序员必须掌握的数据结构:HashMap

news2025/1/13 13:53:28

HashMap底层原理实现是每个Java Boy必须掌握的基本技能,HashMap也是业务开发每天都需要遇到的好伙伴。如此基础且核心的底层数据结构,JDK也给其赋予了线程安全的功能类,我们来看看~

🌱以【面试官面试】形式覆盖Java程序员所需掌握的Java核心知识、面试重点,本博客收录在我开源的《Java学习指南》中,会一直完善下去,希望收到大家的 ⭐ Star ⭐支持,这是我创作的最大动力: https://github.com/hdgaadd/JavaGetOffer

在这里插入图片描述

文章目录

    • 1. HashMap内部结构
      • 1.1 键值的添加流程
      • 1.2 红黑树
    • 2. 线程安全的Map
      • 2.1 线程不安全的HashMap
      • 2.2 线程安全的ConcurrentHashMap
      • 2.3 HashTable和ConcurrentHashMap区别
    • 未完待续。。。

1. HashMap内部结构

面试官:你说下HashMap的内部结构?

好的面试官。

HashMap内部存储数据的对象是一个实现Entry接口的Node数组,也称为哈希桶transient Node<K,V>[] table,后面我们称Node数组为Entry数组。Entry数组初始的大小是16

Node节点的内部属性key、value分别代表键和值,hash代表key的hash值,而next则是指向下一个链表节点的指针。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

1.1 键值的添加流程

面试官:那一个键值是怎么存储到HashMap的?

首先会调用hash方法计算key的hash值,通过key的hashCode值与key的hashCode高16位进行异或运算,使hash值更加随机与均匀。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

再通过该hash值与Entry数组的长度相与,得到要存储到的索引位置int index = (table.length - 1) & hash。如果该索引位置是空的,会把键值直接添加到表头,如果哈希冲突了则会用链表法形成一条链表。

数据添加后,会判断当前容量是否到达了threshold阈值,threshold等于负载因子loadFactor * table.length。负载因子默认是0.75,threshold第一次扩容时为0.75 * 16 = 12

如果到达阈值了则会对Entry数组进行扩容,扩容成为原来两倍容量的Entry数组。

1.2 红黑树

面试官:HashMap链表还会转换成什么?

当链表长度 >= 8时,会把链表转换为红黑树

是这样的,HashMap的链表元素如果数量过多,查询效率会越来越低,所以需要将链表转换为其他数据结构。而二叉搜索树这种数据结构是绝对的子树平衡,左节点比父节点小,右节点比父节点大,在极端情况会退化为链表结构

而红黑树放弃了绝对的子树平衡,转而追求的是一种大致平衡,在极端情况下的数据查询效率更优。

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}

2. 线程安全的Map

2.1 线程不安全的HashMap

面试官:HashMap为什么线程不安全?

一、在多线程环境下,可能会出现数据覆盖的问题。

例如前面提到如果索引位置为空则直接添加到表头,如下面源码所示。此时如果有两个线程同时进入if语句,线程A把数据插入到表头,接着线程B把他的数据覆盖到表头,这样就产生了数据覆盖的问题,线程A的数据相当于消失了。

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

二、另外在多线程环境下,还可能会出现数据不一致的问题。

在插入数据后,判断是否需要扩容是以下代码。

if (++size > threshold)
	resize();

若两个线程同时进入了++size代码块,对size的修改分为三个步骤:读取、计算、赋值。线程A、线程B同时读取了size是0,两者计算时size都为1,后面赋值时把size = 1赋值给了size两次。

但实际上期望的size应该是2,此时就出现了数据不一致的问题,Entry数组的容量会出现错误。

2.2 线程安全的ConcurrentHashMap

面试官:有线程安全的Map吗?

有的,JDK提供了线程安全的ConcurrentHashMap。

ConcurrentHashMap对于底层Entry数组、size容量都添加了可见性的修饰,保证了其他线程能实时监听到该值的最新修改

transient volatile Node<K,V>[] table;
private transient volatile int sizeCtl;

在添加键值的操作,对元素级别进行加锁。若该索引位置不存在元素,则使用乐观锁CAS操作来添加值,而CAS是原子操作,不怕多线程的干扰。

在这里插入图片描述

若该索引位置存在元素,则使用synchronized对该索引位置的头节点进行加锁操作,保证整条链表同一时刻只有一个线程在进行操作。

在这里插入图片描述

另外在JDK7版本中ConcurrentHashMap的实现和JDK8不同。

JDK7版本的数据结构是大数组Segment + 小数组HashEntry,其中小数组HashEntry的每个元素是一条链表,一个Segment是一个HashEntry数组。对每个Segment即每个分段,使用ReentrantLock进行加锁操作。

可以看到JDK8版本相比JDK版本的实现锁粒度更小,且JDK8版本的链表还可以升级为查询效率高的红黑树,所以JDK7版本的ConcurrentHashMap目前被JDK8版本的代替了。

2.3 HashTable和ConcurrentHashMap区别

面试官:HashTable和ConcurrentHashMap有什么区别吗?

HashTable也是线程安全的Map,不过它不仅对修改操作添加加锁操作,获取操作也进行了加锁。

public synchronized V put(K key, V value)
public synchronized V get(Object key)

而ConcurrentHashMap没有对get进行加锁处理,不适用于强一致性的场景。例如要求获取操作必须严格获取到最新的值,这种强一致性场景则更适合使用HashTable。

另外HashTable和HashMap、ConcurrentHashMap还有以下不同。

  1. HashTable继承了Dictionary,而HashMap、ConcurrentHashMap继承了AbstractMap
  2. HashTable初始容量为11,HashMap、ConcurrentHashMap是16
  3. HashTable扩容为原来的2n+1,HashMap、ConcurrentHashMap是扩容为原来的2n

未完待续。。。

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️

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

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

相关文章

使用Kimi快速完成高质量学术论文全流程攻略!

点击下方▼▼▼▼链接直达AIPaperPass &#xff01; AIPaperPass - AI论文写作指导平台 目录 01.论文选题(重要指数:★★★★★) 02.摘要(重要指数:★★★★) 03.关键词(重要指数:★★★★) 04.引言(重要指数:★★★★) 05.正文(重要指数:★★★★★) 06.结论(重要指数…

能源监控可视化大屏的价值,不要说没啥用了,容易暴露格局

能源监控可视化大屏具有以下几个方面的价值&#xff1a; 实时监控&#xff1a; 能源监控可视化大屏可以实时展示能源系统的运行状态&#xff0c;包括电力、水、气等能源的消耗、供应情况&#xff0c;以及设备运行状态等。通过实时监控&#xff0c;可以及时发现异常情况和故障…

翻译《The Old New Thing》 - What does SHGFI_USEFILEATTRIBUTES mean?

What does SHGFI_USEFILEATTRIBUTES mean? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20040601-00/?p39073 Raymond Chen 2004年06月01日 在使用 SHGetFileInfo 函数时&#xff0c;你可以设置一个名为 SHGFI_USEFILEATTRIBUTES 的标志…

目标检测——3D玩具数据集

在数字化时代&#xff0c;计算机视觉技术取得了长足的进展&#xff0c;其中基于形状的3D物体识别技术更是引起了广泛关注。该技术不仅有助于提升计算机对现实世界物体的感知能力&#xff0c;还在多个领域展现出了广阔的应用前景。本文将探讨基于形状的3D物体识别实验的重要性意…

WMS之添加View

目录 前言一、addview示例二、addview流程2.1 流程图2.2 流程分析2.2.1 Actitity的启动流程创建PhoneWindow和DecorView2.2.2.WindowManagerImpl 添加view2.2.3 ViewRootImpl.setView 三、总结 前言 WMS 功能繁杂&#xff0c;通过添加View流程进一步分析WMS 通过本文了解掌握…

RPC分布式通信框架

在实际开发中单机服务器存在诸多问题&#xff1a; 1.受限于硬件资源无法提高并发量 2.任意模块的修改都将导致整个项目代码重新编译部署 3.在系统中&#xff0c;有些模块属于CPU密集型&#xff0c;有些属于I/O密集型&#xff0c;各模块对于硬件资源的需求不一样 什么是分布式&a…

程序员转技术管理要做哪些努力?

对许多开发者而言&#xff0c;深耕技术&#xff0c;然后成为技术专家或许是职业发展的唯一答案。但如果你赞同「软件开发只是我众多职业目标中的一个」&#xff0c;也许你可以试试「技术管理之路」。 我原来觉得和计算机打交道比跟人打交道轻松得多&#xff0c;所以我成了一名…

每日OJ题_DFS回溯剪枝①_力扣46. 全排列(回溯算法简介)

目录 回溯算法简介 力扣46. 全排列 解析代码 回溯算法简介 回溯算法是一种经典的递归算法&#xff0c;通常⽤于解决组合问题、排列问题和搜索问题等。 回溯算法的基本思想&#xff1a;从一个初始状态开始&#xff0c;按照⼀定的规则向前搜索&#xff0c;当搜索到某个状态无…

【UnityShader入门精要学习笔记】第十一章 Shader动画

本系列为作者学习UnityShader入门精要而作的笔记&#xff0c;内容将包括&#xff1a; 书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更&#xff0c;有始无终 我的GitHub仓库 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 UnityShad…

Vuforia AR篇(三)— AR模型出场效果

目录 前言一、AR模型出场二、AR出场特效三、添加过渡效果四、效果 前言 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习&#xff0c;本文就介绍了机器学习的基础内容。 一、AR模型出场 创建ARCamer…

上位机图像处理和嵌入式模块部署(树莓派4b之mcu固件升级)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 在一个系统当中&#xff0c;可能不止需要树莓派4b一个设备&#xff0c;有的时候还需要搭载一个mcu&#xff0c;做一些运动控制的事情。比如说&…

【ESP32入门实战】初识ESP32

【ESP32入门实战】初识ESP32 文章目录 【ESP32入门实战】初识ESP32&#x1f468;‍&#x1f3eb;前言【写作缘由】&#x1f9d1;‍&#x1f393;ESP32介绍&#x1f469;‍&#x1f4bb;ESP32-WROOM-32&#x1f469;‍&#x1f4bb;ESP32的组成部分 &#x1f468;‍&#x1f3eb…

Android—统一依赖版本管理

依赖版本管理有多种方式 config.gradle 用于Groovy DSL&#xff0c;新建一个 config.gradle 文件&#xff0c;然后将项目中所有依赖写在里面&#xff0c;更新只需修改 config.gradle文件内容&#xff0c;作用于所有module buildSrc 可用于Kotlin DSL或Groovy DSL&#xff0c;…

48-70V降12V/33V 5A高效同步降压DC-DC——AH1007

AH1007是一款高效率、高压外置MOSFET管降压芯片TEL&#xff1a;186*4884*3702*&#xff0c;芯片典型输入是8V~100V,输出 电压可调&#xff0c;AH1007最大输出电流可支持6A以上&#xff0c;需要注意板子的散热和温升。 AH1007典型开关频率为150KHz。轻载时会自动降低开关频率以…

如何把MP3音频转AAC?超级简单的音频格式转换方法在这里

在数字化时代&#xff0c;音乐文件的格式多种多样&#xff0c;每种格式都有其独特的特点和优势。其中&#xff0c;MP3和AAC是两种非常常见的音频格式。MP3由于其较小的文件大小和良好的音质&#xff0c;在过去几十年中一直备受欢迎。然而&#xff0c;随着技术的进步和音频编码算…

掼蛋游戏中的坏习惯

掼蛋是一款需要团队合作和策略思考的游戏&#xff0c;已经成为很多人的日常休闲娱乐方式。然而在日常掼蛋游戏中&#xff0c;有些玩家可能会做出一些不良举动&#xff0c;影响游戏的进行。我们列举了一些常见的坏习惯&#xff0c;希望玩家能够注意并且避免。 1、随意退出 有些玩…

SpringCloud之Feign集成Ribbon

Feign定义【可跳过】 Spring Cloud Feign是一个声明式的伪Http客户端&#xff0c;它使得写Http客户端变得更简单。其英文表意为“假装&#xff0c;伪装&#xff0c;变形”&#xff0c;是一个http请求调用的轻量级框架&#xff0c;可以以Java接口注解的方式调用Http请求&#x…

Capture CIS设计小诀窍系列--Capture CIS配置-数据库搭建及ODBC配置

背景介绍&#xff1a;在原理图设计过程中&#xff0c;如果物料信息出现问题&#xff0c;导致BOM错误或者原理图符号、封装不对应&#xff0c;可能会导致项目延期甚至生产事故&#xff0c;严重影响产品设计效率。而Capture CIS原理图设计工具提供的CIS(Component Information Sy…

排队算法的matlab仿真,带GUI界面

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 M/M/1 单服务台单通道排队模型 4.2 M/M/k 多服务台排队模型 4.3 M/G/1 和 G/M/1 模型 5.完整程序 1.程序功能描述 排队算法的matlab仿真,带GUI界面。分别仿真单队列单服务台&#xff…

No system certificates available. Try installing ca-certificates.

一、错误重现 Certificate verification failed: The certificate is NOT trusted. No system certificates available. Try installing ca-certificates. 具体如图 系统环境是ubuntu:22.04 ARM架构 二、解决方法 1、先不要更换镜像源 直接设置 apt update apt -y instal…