三、集合原理-3.2、HashMap(下)
3.2、HashMap(下)
3.2.2、单线程下的HashMap的工作原理(底层逻辑)是什么?
答:
HashMap的源码位于Java的标准库中,你可以在java.util
包中找到它。
以下是HashMap的简化源码示例,用于说明其实现逻辑:
public class HashMap<K, V> {private Entry<K, V>[] table;private static final int DEFAULT_CAPACITY = 16;private int size;public HashMap() {table = new Entry[DEFAULT_CAPACITY];size = 0;}public void put(K key, V value) {int index = getIndex(key); //获取键的哈希值Entry<K, V> newEntry = new Entry<>(key, value);if (table[index] == null) {table[index] = newEntry;} else {Entry<K, V> entry = table[index];while (entry.next != null) {if (entry.key.equals(key)) {entry.value = value;return;}entry = entry.next;}entry.next = newEntry;}size++;}public V get(K key) {int index = getIndex(key);if (table[index] != null) {Entry<K, V> entry = table[index];while (entry != null) {if (entry.key.equals(key)) {return entry.value;}entry = entry.next;}}return null;}private int getIndex(K key) {return key.hashCode() % table.length;}private static class Entry<K, V> {private K key;private V value;private Entry<K, V> next;public Entry(K key, V value) {this.key = key;this.value = value;this.next = null;}}
}
上面的示例中,HashMap使用一个`Entry`数组`table`来存储键值对。
每个`Entry`对象包含了键值对的信息,以及一个`next`指针,用于解决哈希冲突的链表。在`put`方法中,首先根据键的哈希值计算出数组中的索引位置,然后判断该位置是否已经有元素存在。
如果该位置为空,直接存储新的`Entry`对象;
如果该位置已经有元素存在,需要通过链表遍历找到对应的键值对,如果找到键相同的元素,则更新值;
如果链表中没有相同的键,将新的`Entry`对象添加到链表的末尾。在`get`方法中,也是根据键的哈希值计算出数组中的索引位置,然后遍历链表找到对应的键值对。
如果找到了键相同的元素,返回对应的值;如果没有找到,返回`null`。需要注意的是,上面的示例只是HashMap的简化版,实际的HashMap还包含了很多其他的方法和逻辑,如扩容、迭代器、并发控制等。
如果你对HashMap的详细实现感兴趣,你可以查阅Java标准库中HashMap的源码。
3.2.3、HashMap是如何解决Hash冲突的?
答:
在Java中,HashMap使用了链地址法(chaining)来解决哈希冲突。具体来说,HashMap内部使用一个数组来存储键值对,每个数组元素称为桶(bucket)。
当HashMap要存储一个键值对时,它首先会通过键的hashCode()方法计算出哈希码。然后,根据哈希码通过取模运算得到桶的索引位置。如果该桶为空,即没有发生哈希冲突,则直接将键值对存储在该桶中。如果发生了哈希冲突,即两个或更多的键具有相同的哈希码,那么这些键值对将以链表的形式存储在同一个桶中。
当需要从HashMap中获取某个键对应的值时,HashMap首先会通过键的hashCode()方法计算哈希码,然后根据哈希码找到对应的桶。接着,它会遍历该桶中的链表,比较每个键是否与目标键相等(使用equals()方法进行比较)。如果找到了相等的键,则返回对应的值;如果遍历完链表仍未找到相等的键,则返回null。
需要注意的是,当链表长度超过一定阈值(默认为8)时,HashMap会将链表转换为红黑树。这样可以提高在大量键值对时的查找效率。当然,如果链表长度小于等于6时,HashMap会将红黑树转换回链表,以节省内存。
总结起来,HashMap通过链地址法解决了哈希冲突,即将具有相同哈希码的键值对以链表的形式存储在同一个桶中,通过equals方法比较键的值来找到对应的值。
3.2.4、HashMap什么时候扩容?如何自动扩容?
答:
HashMap在插入元素时会检查当前的元素数量是否达到了负载因子(load factor)的阈值。负载因子是指HashMap中存储元素的比例,它的默认值是0.75。当存储的元素数量超过了负载因子乘以HashMap的容量时,就会触发扩容操作。
HashMap的自动扩容操作会创建一个新的更大容量的数组,并将所有元素重新分配到新的数组中。这个过程涉及到重新计算每个元素在新数组中的位置,并将其插入到正确的位置上。扩容操作会导致HashMap内部的散列函数重新计算,以保证插入元素的均匀分布。
在扩容过程中,HashMap会将原有数组中的元素一个个重新计算并插入到新数组中,这个过程是逐个迁移的。具体来说,扩容操作会先将原有的数组元素保存到一个临时变量中,然后创建新的更大容量的数组,并将每个元素重新计算并放入新数组中。这样做的好处是可以避免因为数组容量不足而频繁进行扩容操作,节省了时间和空间。
自动扩容操作可以通过调用HashMap的put()方法来触发,当元素数量超过容量乘以负载因子时,会自动触发扩容操作。同时,也可以通过显式调用HashMap的resize()方法来手动触发扩容操作。
扩展:
扩容操作会涉及到重新计算每个元素在新数组中的位置,并将其插入到正确的位置上。这个过程可能会比较耗时,因此在应用中,最好在事先知道HashMap需要保存的元素数量的情况下,提前设置HashMap的初始容量,以减少扩容操作的次数,提高性能。
3.2.5、为什么HashMap会产生死循环?
答:
在Java中,HashMap的死循环问题通常是由于多个线程同时对HashMap进行并发修改操作引起的。当多个线程同时修改HashMap的结构时,可能会导致HashMap的内部结构不一致,进而导致死循环。
在HashMap中,有一个内部变量modCount用于记录HashMap结构修改的次数。当一个线程在进行迭代操作时,会先保存modCount的值,然后在迭代的过程中比较保存的modCount值和当前HashMap的modCount值。如果两者不一致,则说明HashMap发生了结构修改,迭代操作会抛出ConcurrentModificationException异常。然而,如果多个线程同时对HashMap进行修改,并且修改操作之间的时间非常接近,可能会导致多个线程同时检查modCount值,从而绕过了检查,发生了死循环。
扩展:
为了避免HashMap的死循环问题,可以采取以下措施:
- 使用线程安全的HashMap实现,如ConcurrentHashMap。
- 在并发修改HashMap时,使用同步机制(如synchronized)来保证同一时间只有一个线程对HashMap进行修改。
- 在迭代HashMap的过程中,不要进行修改操作,可以使用Iterator遍历,并通过Iterator的remove方法删除元素。
3.2.6、HashMap和TreeMap的区别是什么?
答:
HashMap和TreeMap都是Map接口的实现类,用于存储键值对。
区别如下:
-
底层数据结构:
- HashMap:使用哈希表(数组+链表/红黑树),根据键的哈希值存储键值对,不保证插入顺序;
- TreeMap:使用红黑树,根据键的自然顺序或者自定义比较器对键进行排序存储。
-
排序特性:
- HashMap:无序存储键值对,根据键的哈希值进行存储和查找,查找效率较高;
- TreeMap:按照键的自然顺序或者自定义比较器对键进行排序,可以实现对键的有序访问。
-
性能:
- HashMap的插入、删除、查询等操作的时间复杂度为O(1)(平均情况下),具有较好的性能;
- TreeMap的插入、删除、查询等操作的时间复杂度为O(log(n)),其中n为元素个数。
-
使用场景:
- HashMap适用于无序存储,对于查找操作的频繁场景,如缓存、查找表等;
- TreeMap适用于需要对键进行有序存储和检索的场景,如需要按照键的顺序遍历、查找等。
扩展:
根据实际需求和使用场景选择合适的数据结构。如果需要有序访问或者按照键的顺序进行操作,使用TreeMap;如果无需有序访问或者对性能要求较高,可以选择HashMap。
3.2.7、为什么ConcurrentHashMap的key不允许为null?
答:
首先要了解,HashMap允许键和值为null。在HashMap中,null被视为一个有效的键或值,并且可以被插入和检索。但是需要注意的是,如果键重复,那么后面插入的键值对会覆盖之前的键值对。
在Java中,ConcurrentHashMap的key不允许为null的原因是为了保证数据的一致性和避免引发潜在的问题。
ConcurrentHashMap是一个线程安全的哈希表,它通过将数据划分为多个段来实现并发访问的高效率。每个段都拥有一个独立的锁,可以同时被多个线程访问,这样就可以减少线程间的竞争。
然而,如果允许key为null,就会导致在查询、插入和删除等操作时出现潜在的问题。在ConcurrentHashMap中,对于每个key的哈希值都会通过哈希函数计算得出一个索引,用于确定该key在哪个段中存储。但如果key为null,由于无法计算hash值,就无法确定key应该存储在哪个段中。
另外,ConcurrentHashMap的实现中使用了一些特殊的技术,例如偏移量等,用于提高并发性能。如果允许key为null,就会引发一些复杂的问题,如指针偏移计算错误等,可能导致数据的不一致性和线程安全性问题。
因此,为了保证ConcurrentHashMap的正确性和一致性,不允许key为null。如果需要存储null值,可以将null作为value存储,或者使用其他的数据结构来代替ConcurrentHashMap。
3.2.8、谈谈你对ConcurrentHashMap底层实现原理的理解
答:
ConcurrentHashMap是Java中用于高并发环境下线程安全的哈希表实现。它的底层实现原理主要涉及了以下几个方面:
-
分段锁:ConcurrentHashMap将整个哈希表分成了一系列的小段(segment),每个段都有自己的锁。这种分段的设计使得多个线程可以同时访问不同的段,从而提高了并发性能。因此,在读操作时,只需要锁住特定的段,而不是整个哈希表。这样就允许了多个线程同时读取不同的段,提高了并发性能。而对于写操作,需要锁住对应的段,保证数据一致性。
-
CAS操作:ConcurrentHashMap使用了CAS(Compare and Swap)操作来实现线程安全。CAS是一种无锁的原子操作,通过比较内存中的值和预期值,如果相等则更新为新的值。这种机制使得ConcurrentHashMap在并发环境下能够实现高效的数据修改和更新。
-
链表和红黑树:在ConcurrentHashMap中,每个段(segment)内部是一个类似于HashMap的结构,使用链表来解决哈希冲突。当链表长度过长时,会将链表转化为红黑树,以提高查找的效率。这种设计使得ConcurrentHashMap既能在小规模数据下保持高效,又能在大规模数据下提供良好的性能。
-
扩容:ConcurrentHashMap采用了分段锁机制,使得在扩容时只需要锁住正在被操作的段,而其他段仍然可以继续读写。这种设计使得ConcurrentHashMap的扩容操作对于并发性能影响较小。
扩展:
总结:ConcurrentHashMap是通过分段锁、CAS操作、链表和红黑树等技术来实现线程安全和高并发的特性。这些设计和实现机制使得ConcurrentHashMap成为Java中常用的线程安全的哈希表实现。
3.2.9、ConcurrentHashMap是如何保证线程安全的?
答:
ConcurrentHashMap是Java中线程安全的哈希表实现,它通过以下几个机制来保证线程安全:
-
分段锁:ConcurrentHashMap内部使用一个Segment数组来存储键值对,每个Segment具有自己的锁。这样不同的线程可以同时访问不同的Segment,从而减小了锁的粒度,提高了并发性能。
-
读写分离:ConcurrentHashMap允许多个线程同时读取数据,而不会阻塞。读操作不需要获得锁,只有写操作需要获得锁,并且会阻塞其他的读写操作。这样可以提高并发读的性能。
-
CAS操作:ConcurrentHashMap使用CAS(Compare and Swap)操作来保证线程安全。CAS是一种无锁的原子操作,通过比较当前值和期望值是否相等来决定是否更新。如果多个线程同时进行CAS操作,只有一个线程会成功,其他线程需要重试。这样可以避免使用锁带来的性能损失。
相关文章:
三、集合原理-3.2、HashMap(下)
3.2、HashMap(下) 3.2.2、单线程下的HashMap的工作原理(底层逻辑)是什么? 答: HashMap的源码位于Java的标准库中,你可以在java.util包中找到它。 以下是HashMap的简化源码示例,用于说明其实现逻辑&#…...
【激活函数】Activation Function——在卷积神经网络中的激活函数是一个什么样的角色??
【激活函数】Activation Function——在卷积神经网络中的激活函数是一个什么样的角色?? Activation Function——在卷积神经网络中的激活函数是一个什么样的角色?? 文章目录 【激活函数】Activation Function——在卷积神经网络中…...
重生之我在Java世界------学单例设计模式
什么是单例设计模式? 单例模式是面向对象编程中最简单却又最常用的设计模式之一。它的核心思想是确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的原理、常见实现方法、优缺点,以及在使用过程中可能遇到的陷阱。 单…...
快速提升Python Pandas处理速度的秘诀
大家好,Python的Pandas库为数据处理和分析提供了丰富的功能,但当处理大规模数据时,性能问题往往成为瓶颈。本文将介绍一些在Pandas中进行性能优化的方法与技巧,帮助有效提升数据处理速度,优化代码运行效率。 1.数据类…...
在基于线程的环境中运行 MATLAB 函数
MATLAB 和其他工具箱中的数百个函数可以在基于线程的环境中运行。可以使用 backgroundPool 或 parpool("threads") 在基于线程的环境中运行代码。 要在后台运行函数,请使用 parfeval 和 backgroundPool。 具体信息可以参考Choose Between Thread-B…...

黑神话悟空+云技术,游戏新体验!
近期,一款名为黑神话悟空的游戏因其独特的艺术风格和创新的技术实现在玩家中产生了不小的影响。 而云桌面技术作为一种新兴的解决方案,正在改变人们的游戏体验方式,使得高性能游戏可以在更多设备上流畅运行。 那么,黑神话悟空如…...

【Android 13源码分析】WindowContainer窗口层级-3-实例分析
在安卓源码的设计中,将将屏幕分为了37层,不同的窗口将在不同的层级中显示。 对这一块的概念以及相关源码做了详细分析,整理出以下几篇。 【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树 【Android 13源码分析】WindowCon…...

Redis常用操作及springboot整合redis
1. Redis和Mysql的区别 数据模型:二者都是数据库,但是不同的是mysql是进行存储到磁盘当中,而Redis是进行存储到内存中. 数据模型 : mysql的存储的形式是二维表而Redis是通过key-value键值对的形式进行存储数据. 实际的应用的场景: Redis适合于需要快速读写的场景&…...
动态规划day34|背包理论基础(1)(2)、46.携带研究材料(纯粹的01背包)、416. 分割等和子集(01背包的应用)
动态规划day34|背包理论基础(1)(2)、46.携带研究材料、416. 分割等和子集 背包理论基础(1)——二维背包理论基础(2)——一维46.携带研究材料(卡码网 01背包)1. 二维背包2. 一维背包 …...
pytorch优化器
在反向传播计算完所有参数的梯度后,还需要使用优化方法更新网络的权重和参数。例如,随机梯度下降法(SGD)的更新策略如下: weight weight - learning_rate * gradient 手动实现如下: learning_rate 0.01 …...

必备工具,AI生成证件照,再也不用麻烦他人,电子驾驶证等多种证件照一键生成
最近有一个生成证件照的开源项目很火,今天我们来学习一下。之前我生成证件照都是线下去拍照,线上使用也是各种限制,需要付费或看广告,而且效果也不是很理想, 今天要分享的这个 AI 证件照生成工具可以一键可以生成一寸…...

深度解析 MintRich 独特的价格曲线机制玩法
随着 Meme 币赛道的迅速崛起,NFT 市场也迎来了新的变革。作为一个创新的 NFT 发行平台,Mint.Rich 正掀起一场全民参与的 NFT 热潮。其简易的操作界面和独特的价格曲线设计,让任何人都能以极低的门槛发行和交易自己的 NFT,从而参与…...

实时数仓3.0DWD层
实时数仓3.0DWD层 DWD层设计要点:9.1 流量域未经加工的事务事实表9.1.1 主要任务9.1.2 思路9.1.3 图解9.1.4 代码 9.2 流量域独立访客事务事实表9.2.1 主要任务9.2.2 思路分析9.2.3 图解9.2.4 代码 9.3 流量域用户跳出事务事实表9.3.1 主要任务9.3.2 思路分析9.3.3 …...

路径规划 | 基于A*算法的往返式全覆盖路径规划的改进算法(Matlab)
目录 效果一览基本介绍程序设计参考文献 效果一览 基本介绍 基于A*算法的往返式全覆盖路径规划的改进算法 matlab实现代码 往返式全覆盖路径规划,通过建立二维栅格地图,设置障碍物,以及起始点根据定义往返式路径规划的定义的优先级运动规则从…...

QT 串口上位机读卡显示
目录 一. QT创建工程 二. 软件更换图标 三. QT打包 一. QT创建工程 文件新建,选择创建一个桌面QT。 重命名RFID,并选择工程保存路径 RFID.pro QT core gui serialport #串行串口greaterThan(QT_MAJOR_VERSION, 4): QT widgetsTARGET RFID TE…...

Chrome谷歌浏览器登录账号next无反应
文章目录 问题描述 我们的Chrome浏览器在更新之后,会出现登录谷歌账号的时候,当你输入你的谷歌邮箱之后,点击 n e x t next next,也就是下一步的时候,页面没有反应,也就是没有跳转到输入密码的页面。 分析 根据logs里…...

Android相关线程基础
线程基础 进程与线程 进程:可以被看做是程序的实体, 是系统进行资源分配和调度的基本单位. 线程:是操作系统调度的最小单元, 也叫轻量级进程 使用多线程的优点 可以减少程序的响应时间。如果某个操作很耗时, 能够避免陷入长时间的等待, 从而有着更好的交互性. 线程较之进…...

uniapp 如何自定义导航栏并自适应机型
如今的移动设备有各种不同的屏幕形状,如刘海屏、水滴屏等。这些异形屏会影响页面的布局,尤其是导航栏和底部栏的显示。通过获取安全区域信息,可以确保页面内容不会被异形屏的特殊区域遮挡。 在设计页面顶部导航栏时,可以根据 saf…...

Java高级Day43-类加载
117.类加载 静态和动态加载 反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强 动态加载:运行时加载需要的类,如果运行时不用该类…...
【LeetCode 算法笔记】155. 最小栈
目录 问题描述单个栈实现双栈实现不开辟额外空间 问题描述 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。 void push(int val) 将元素val推入堆栈。 void pop()…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
41道Django高频题整理(附答案背诵版)
解释一下 Django 和 Tornado 的关系? Django和Tornado都是Python的web框架,但它们的设计哲学和应用场景有所不同。 Django是一个高级的Python Web框架,鼓励快速开发和干净、实用的设计。它遵循MVC设计,并强调代码复用。Django有…...