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

三、集合原理-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的死循环问题,可以采取以下措施:

  1. 使用线程安全的HashMap实现,如ConcurrentHashMap。
  2. 在并发修改HashMap时,使用同步机制(如synchronized)来保证同一时间只有一个线程对HashMap进行修改。
  3. 在迭代HashMap的过程中,不要进行修改操作,可以使用Iterator遍历,并通过Iterator的remove方法删除元素。

3.2.6、HashMap和TreeMap的区别是什么?

答:
HashMap和TreeMap都是Map接口的实现类,用于存储键值对。

区别如下:

  1. 底层数据结构:

    • HashMap:使用哈希表(数组+链表/红黑树),根据键的哈希值存储键值对,不保证插入顺序;
    • TreeMap:使用红黑树,根据键的自然顺序或者自定义比较器对键进行排序存储。
  2. 排序特性:

    • HashMap:无序存储键值对,根据键的哈希值进行存储和查找,查找效率较高;
    • TreeMap:按照键的自然顺序或者自定义比较器对键进行排序,可以实现对键的有序访问。
  3. 性能:

    • HashMap的插入、删除、查询等操作的时间复杂度为O(1)(平均情况下),具有较好的性能;
    • TreeMap的插入、删除、查询等操作的时间复杂度为O(log(n)),其中n为元素个数。
  4. 使用场景:

    • 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中用于高并发环境下线程安全的哈希表实现。它的底层实现原理主要涉及了以下几个方面:

  1. 分段锁:ConcurrentHashMap将整个哈希表分成了一系列的小段(segment),每个段都有自己的锁。这种分段的设计使得多个线程可以同时访问不同的段,从而提高了并发性能。因此,在读操作时,只需要锁住特定的段,而不是整个哈希表。这样就允许了多个线程同时读取不同的段,提高了并发性能。而对于写操作,需要锁住对应的段,保证数据一致性。

  2. CAS操作:ConcurrentHashMap使用了CAS(Compare and Swap)操作来实现线程安全。CAS是一种无锁的原子操作,通过比较内存中的值和预期值,如果相等则更新为新的值。这种机制使得ConcurrentHashMap在并发环境下能够实现高效的数据修改和更新。

  3. 链表和红黑树:在ConcurrentHashMap中,每个段(segment)内部是一个类似于HashMap的结构,使用链表来解决哈希冲突。当链表长度过长时,会将链表转化为红黑树,以提高查找的效率。这种设计使得ConcurrentHashMap既能在小规模数据下保持高效,又能在大规模数据下提供良好的性能。

  4. 扩容:ConcurrentHashMap采用了分段锁机制,使得在扩容时只需要锁住正在被操作的段,而其他段仍然可以继续读写。这种设计使得ConcurrentHashMap的扩容操作对于并发性能影响较小。

扩展:
总结:ConcurrentHashMap是通过分段锁、CAS操作、链表和红黑树等技术来实现线程安全和高并发的特性。这些设计和实现机制使得ConcurrentHashMap成为Java中常用的线程安全的哈希表实现。

3.2.9、ConcurrentHashMap是如何保证线程安全的?

答:
ConcurrentHashMap是Java中线程安全的哈希表实现,它通过以下几个机制来保证线程安全:

  1. 分段锁:ConcurrentHashMap内部使用一个Segment数组来存储键值对,每个Segment具有自己的锁。这样不同的线程可以同时访问不同的Segment,从而减小了锁的粒度,提高了并发性能。

  2. 读写分离:ConcurrentHashMap允许多个线程同时读取数据,而不会阻塞。读操作不需要获得锁,只有写操作需要获得锁,并且会阻塞其他的读写操作。这样可以提高并发读的性能。

  3. CAS操作:ConcurrentHashMap使用CAS(Compare and Swap)操作来保证线程安全。CAS是一种无锁的原子操作,通过比较当前值和期望值是否相等来决定是否更新。如果多个线程同时进行CAS操作,只有一个线程会成功,其他线程需要重试。这样可以避免使用锁带来的性能损失。

相关文章:

三、集合原理-3.2、HashMap(下)

3.2、HashMap&#xff08;下&#xff09; 3.2.2、单线程下的HashMap的工作原理(底层逻辑)是什么&#xff1f; 答&#xff1a; HashMap的源码位于Java的标准库中&#xff0c;你可以在java.util包中找到它。 以下是HashMap的简化源码示例&#xff0c;用于说明其实现逻辑&#…...

【激活函数】Activation Function——在卷积神经网络中的激活函数是一个什么样的角色??

【激活函数】Activation Function——在卷积神经网络中的激活函数是一个什么样的角色&#xff1f;&#xff1f; Activation Function——在卷积神经网络中的激活函数是一个什么样的角色&#xff1f;&#xff1f; 文章目录 【激活函数】Activation Function——在卷积神经网络中…...

重生之我在Java世界------学单例设计模式

什么是单例设计模式&#xff1f; 单例模式是面向对象编程中最简单却又最常用的设计模式之一。它的核心思想是确保一个类只有一个实例&#xff0c;并提供一个全局访问点。本文将深入探讨单例模式的原理、常见实现方法、优缺点&#xff0c;以及在使用过程中可能遇到的陷阱。 单…...

快速提升Python Pandas处理速度的秘诀

大家好&#xff0c;Python的Pandas库为数据处理和分析提供了丰富的功能&#xff0c;但当处理大规模数据时&#xff0c;性能问题往往成为瓶颈。本文将介绍一些在Pandas中进行性能优化的方法与技巧&#xff0c;帮助有效提升数据处理速度&#xff0c;优化代码运行效率。 1.数据类…...

在基于线程的环境中运行 MATLAB 函数

MATLAB 和其他工具箱中的数百个函数可以在基于线程的环境中运行。可以使用 backgroundPool 或 parpool("threads") 在基于线程的环境中运行代码。 ​要在后台运行函数&#xff0c;请使用 parfeval 和 backgroundPool。​ ​具体信息可以参考Choose Between Thread-B…...

黑神话悟空+云技术,游戏新体验!

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

【Android 13源码分析】WindowContainer窗口层级-3-实例分析

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

Redis常用操作及springboot整合redis

1. Redis和Mysql的区别 数据模型&#xff1a;二者都是数据库,但是不同的是mysql是进行存储到磁盘当中,而Redis是进行存储到内存中. 数据模型 : mysql的存储的形式是二维表而Redis是通过key-value键值对的形式进行存储数据. 实际的应用的场景: Redis适合于需要快速读写的场景&…...

动态规划day34|背包理论基础(1)(2)、46.携带研究材料(纯粹的01背包)、416. 分割等和子集(01背包的应用)

动态规划day34|背包理论基础&#xff08;1&#xff09;&#xff08;2&#xff09;、46.携带研究材料、416. 分割等和子集 背包理论基础&#xff08;1&#xff09;——二维背包理论基础&#xff08;2&#xff09;——一维46.携带研究材料(卡码网 01背包)1. 二维背包2. 一维背包 …...

pytorch优化器

在反向传播计算完所有参数的梯度后&#xff0c;还需要使用优化方法更新网络的权重和参数。例如&#xff0c;随机梯度下降法&#xff08;SGD&#xff09;的更新策略如下&#xff1a; weight weight - learning_rate * gradient 手动实现如下&#xff1a; learning_rate 0.01 …...

必备工具,AI生成证件照,再也不用麻烦他人,电子驾驶证等多种证件照一键生成

最近有一个生成证件照的开源项目很火&#xff0c;今天我们来学习一下。之前我生成证件照都是线下去拍照&#xff0c;线上使用也是各种限制&#xff0c;需要付费或看广告&#xff0c;而且效果也不是很理想&#xff0c; 今天要分享的这个 AI 证件照生成工具可以一键可以生成一寸…...

深度解析 MintRich 独特的价格曲线机制玩法

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

实时数仓3.0DWD层

实时数仓3.0DWD层 DWD层设计要点&#xff1a;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实现代码 往返式全覆盖路径规划&#xff0c;通过建立二维栅格地图&#xff0c;设置障碍物&#xff0c;以及起始点根据定义往返式路径规划的定义的优先级运动规则从…...

QT 串口上位机读卡显示

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

Chrome谷歌浏览器登录账号next无反应

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

Android相关线程基础

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

uniapp 如何自定义导航栏并自适应机型

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

Java高级Day43-类加载

117.类加载 静态和动态加载 反射机制是java实现动态语言的关键&#xff0c;也就是通过反射实现类动态加载 静态加载&#xff1a;编译时加载相关的类&#xff0c;如果没有则报错&#xff0c;依赖性太强 动态加载&#xff1a;运行时加载需要的类&#xff0c;如果运行时不用该类…...

【LeetCode 算法笔记】155. 最小栈

目录 问题描述单个栈实现双栈实现不开辟额外空间 问题描述 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。 void push(int val) 将元素val推入堆栈。 void pop()…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

Golang——7、包与接口详解

包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...