【Java集合类篇】HashMap的数据结构是怎样的?

HashMap的数据结构是怎样的?
- ✔️HashMap的数据结构
- ✔️ 数组
- ✔️ 链表
✔️HashMap的数据结构
在Java中,保存数据有两种比较简单的数据结构: 数组和链表(或红黑树)。
HashMap是Java中常用的数据结构,它实现了Map接口。HashMap通过键值对的形式存储数据,其中键是唯一的,而值可以是任何对象。HashMap底层使用数组和链表(或红黑树)来实现。
常用的哈希函数的冲突解决办法中有一种方法叫做链地址法,其实就是将数组和链表组合在一起,发挥了两者的优势,我们可以将其理解为链表的数组。在JDK 1.8之前,HashMap就是通过这种结构来存储数据的。

我们可以从上图看到,左边很明显是个数组,数组的每个成员是一个链表。该数据结构所容纳的所有元素均包含一个指针,用于元素间的链接。我们根据元素的自身特征把元素分配到不同的链表中去,反过来我们也正是通过这些特征找到正确的链表,再从链表中找出正确的元素。其中,根据元素特征计算元素数组下标的方法就是哈希算法,即本文的主角 hash() 函数 (当然,还包括indexOf()函数)。
✔️ 数组
数组:
HashMap使用一个数组来存储键值对。数组的每个元素都是一个桶(bucket),桶中存储着一个链表(LinkedList)或红黑树(TreeMap)。桶的数量可以根据需要动态调整。数组的索引方式采用哈希算法,通过将键的哈希值对数组长度取模来得到对应的桶。
数组的特点是:寻址容易,插入和删除困难。
看一个如何使用数组实现HashMap的代码片段:
public class MyHashMap<K, V> { // 默认初始容量 private static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认加载因子 private static final float DEFAULT_LOAD_FACTOR = 0.75f; // 存储键值对的数组 private Entry<K, V>[] table; // 当前容量 private int capacity; // 实际存储的键值对数量 private int size; public MyHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } public MyHashMap(int capacity) { this(capacity, DEFAULT_LOAD_FACTOR); } public MyHashMap(int capacity, float loadFactor) { this.capacity = capacity; table = new Entry[capacity]; size = 0; } public V put(K key, V value) { int hash = hash(key); int index = indexFor(hash, table.length); Entry<K, V> oldValue = table[index]; if (oldValue == null) { table[index] = new Entry<>(key, value, null); size++; if (size > capacity * loadFactor) { rehash(); } } else { Entry<K, V> newEntry = new Entry<>(key, value, oldValue); table[index] = newEntry; } return oldValue == null ? null : oldValue.value; } public V get(K key) { int hash = hash(key); int index = indexFor(hash, table.length); Entry<K, V> entry = table[index]; if (entry != null && Objects.equals(entry.key, key)) { return entry.value; } else { return null; } } public int size() { return size; } private int hash(Object key) { return Objects.hashCode(key); } private int indexFor(int hash, int length) { return hash % length; } private void rehash() { Entry<K, V>[] oldTable = table; int oldCapacity = oldTable.length; int newCapacity = oldCapacity * 2; Entry<K, V>[] newTable = new Entry[newCapacity]; for (Entry<K, V> oldEntry : oldTable) { while (oldEntry != null) { Entry<K, V> next = oldEntry.next; int hash = hash(oldEntry.key); int index = indexFor(hash, newCapacity); oldEntry.next = newTable[index]; newTable[index] = oldEntry; oldEntry = next; } } table = newTable; capacity = newCapacity; }
}
✔️ 链表
链表:当多个键的哈希值映射到同一个桶时,它们会形成一个链表。链表中的每个节点包含一个键值对和指向下一个节点的指针。链表的作用是在插入、删除和查找操作时解决哈希冲突。
链表的特点是: 寻址困难,插入和删除容易
看一个如何使用链表实现HashMap的代码片段,是一个简单的HashMap实现,使用链表来处理哈希冲突:
public class MyHashMap<K, V> { private static class Entry<K, V> { K key; V value; Entry<K, V> next; Entry(K key, V value, Entry<K, V> next) { this.key = key; this.value = value; this.next = next; } } private Entry<K, V>[] table; private int capacity; private int size; private float loadFactor; public MyHashMap(int capacity, float loadFactor) { if (capacity < 0) throw new IllegalArgumentException("Capacity must be non-negative"); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Load factor must be positive"); this.capacity = capacity; this.loadFactor = loadFactor; table = new Entry[capacity]; size = 0; } public V put(K key, V value) { if (key == null) return null; // HashMaps don't allow null keys // If size exceeds load factor * capacity, rehash if (size >= capacity * loadFactor) { rehash(); } int hash = hash(key); int index = indexFor(hash, capacity); Entry<K, V> entry = table[index]; if (entry == null) { // No collision, create new entry table[index] = new Entry<>(key, value, null); size++; } else { // Collision occurred, handle it using chaining while (entry != null && !entry.key.equals(key)) { if (entry.next == null) { // End of chain, insert new entry entry.next = new Entry<>(key, value, null); size++; break; } entry = entry.next; } // If key already exists, update value if (entry != null && entry.key.equals(key)) { V oldValue = entry.value; entry.value = value; return oldValue; } } return null; // If key was new or not found } public V get(K key) { if (key == null) return null; // HashMaps don't allow null keys int hash = hash(key); int index = indexFor(hash, capacity); Entry<K, V> entry = table[index]; while (entry != null && !entry.key.equals(key)) { entry = entry.next; } return entry == null ? null : entry.value; } private void rehash() { capacity *= 2; Entry<K, V>[] oldTable = table; table = new Entry[capacity]; size = 0; for (Entry<K, V> entry : oldTable) { while (entry != null) { Entry<K, V> next = entry.next; int hash = hash(entry.key); int index = indexFor(hash, capacity); entry.next = table[index]; table[index] = entry; size++; entry = next; } } } private int hash(K key) { return Math.abs(key.hashCode()) % capacity; } private int indexFor(int hash, int length) { return hash % length; } public static void main(String[] args) { MyHashMap<String, Integer> map = new MyHashMap<>(16, 0.75f); map.put("one", 1); map.put("two", 2); map.put("three", 3); System.out.println(map.get("one")); // Should print 1 System.out.println(map.get("two")); // Should print 2 System.out.println(map.get("three")); //Should print 3
在JDK 1.8中为了解决因hash冲突导致某个链表长度过长,影响 put 和 get 的效率,引入了红黑树。
关于红黑树,下一篇会作为单独的博文进行更新。
相关文章:
【Java集合类篇】HashMap的数据结构是怎样的?
HashMap的数据结构是怎样的? ✔️HashMap的数据结构✔️ 数组✔️ 链表 ✔️HashMap的数据结构 在Java中,保存数据有两种比较简单的数据结构: 数组和链表(或红黑树)。 HashMap是 Java 中常用的数据结构,它实现了 Map 接口。Has…...
Spring 应用合并之路(一):摸石头过河 | 京东云技术团队
公司在推进降本增效,在尝试多种手段之后,发现应用太多,每个应用都做跨机房容灾部署,则最少需要 4 台机器(称为容器更合适)。那么,将相近应用做一个合并,减少维护项目,提高…...
Android13配置selinux让system应用可读sys,proc,SN号
system权限应用读sys,proc目录及SN号 Android13预置的system应用,需要读/sys, /proc目录,读(SN)serial number号, 需要修改selinux配置,否则会报avc错. 其修改方法会比Android11复杂一些. 实现 system_app.te中添加…...
防勒索病毒攻击的关键措施
【作者】朱向东 中原银行 高级工程师 在当今数字化时代,勒索病毒成为了企业和个人面临的一项严峻威胁。勒索病毒攻击可以导致数据丢失、系统瘫痪以及经济损失。为了保护自己和组织的利益,采取一系列的防范措施是至关重要的。下面是一些关键的措施&#…...
代表团坐车 - 华为OD统一考试
OD统一考试(B卷) 分值: 100分 题解: Java / Python / C++ 题目描述 某组织举行会议,来了多个代表团同时到达,接待处只有一辆汽车可以同时接待多个代表团,为了提高车辆利用率,请帮接待员计算可以坐满车的接待方案输出方案数量。 约束: 一个团只能上一辆车,并且代表团…...
运用Jmeter进行登录测试
开始了解Jmeter,写篇关于Jmeter的博客做备忘,这里以苏宁易购网站的登录请求为例实战来说明测试计划元件,创建一个 Web 测试计划。 今天简单介绍Jemeter的入门,Jmeter 的安装这边就跳过,直接讲述如何使用JMETER,如何运用Jmeter进行测试。 a.下载jmeter软件 b.安装…...
Docker学习与应用(四)-容器数据卷
1、容器数据卷 1)什么是容器数据卷 docker的理念回顾 将应用和环境打包成一个镜像! 数据?如果数据都在容器中,那么我们容器删除,数据就会丢失!需求:数据可以持久化 MySQL,容器删…...
CentOS 7.6下HTTP隧道代理的安全性考虑
在CentOS 7.6上配置HTTP隧道代理时,安全性是一个不可忽视的重要因素。以下是对HTTP隧道代理安全性的一些关键考虑因素: 1. 加密和数据安全 使用强加密算法:确保您使用的是经过广泛认可和强化的加密算法,如AES-256-GCM。数据完整…...
Mockito+junit5搞定单元测试
目录 一、简介1.1 单元测试的特点1.2 Mock类框架的使用场景1.3 常见的Mock框架1.3.1 Mockito1.3.2 EasyMock1.3.3 PowerMock1.3.4 Testable1.3.5 比较 二、Mockito的使用2.1 导入pom文件2.2 mock对象和spy对象2.3 初始化mock/spy对象的方式2.4 参数匹配2.5 方法插桩2.6 InjectM…...
PostgreSQL获取当天、昨天、本月、上个月、本年、去年的数据
gps_time为timestamp类型日期字段 获取当天的数据 WHERE DATE_TRUNC(day, gps_time) CURRENT_DATE --或 WHERE DATE(gps_time) CURRENT_DATE获取昨天的数据 WHERE DATE_TRUNC(day, gps_time) CURRENT_DATE - INTERVAL 1 day获取本月的数据 WHERE DATE_TRUNC(month, gps_…...
XCTF:stage1[WriteUP]
从题目中下载到图片: 考虑图片是png,隐写方式有可能是高宽修改,也可能是色相隐藏,色彩通道位隐藏等等 使用stegsolve对图片进行一下伽马、颜色转换 在图片的左上角就显示出了一个二维码 使用QR_Rresearch工具对二维码扫描 获得一…...
STM32CubeMX教程13 ADC - 单通道转换
目录 1、准备材料 2、实验目标 3、ADC概述 4、实验流程 4.0、前提知识 4.1、CubeMX相关配置 4.1.1、时钟树配置 4.1.2、外设参数配置 4.1.3、外设中断配置 4.2、生成代码 4.2.1、外设初始化调用流程 4.2.2、外设中断调用流程 4.2.3、添加其他必要代码 5、常用函数…...
矩阵的乘法
首先矩阵的乘法定义如下: #include <stdio.h> int main() { int i 0; int j 0; int arr[20][20] { 0 }; int str[20][20] { 0 }; int s[20][20] { 0 }; int n1 0; int n2 0; int m2 0; int z 0; int m1 0;…...
python爬取招聘网站数据
这段代码是使用Selenium自动化测试模块进行网页爬取的示例代码。它通过模拟人的行为在浏览器中操作网页来实现爬取。具体的流程如下: 导入所需的模块,包括Selenium、时间、随机、csv等模块。打开浏览器,创建一个Chrome浏览器实例。设置要爬取…...
灌区信息化方案(什么是现代化灌区,如何一步到位)
一、系统概述 详情:https://www.key-iot.com.cn/ 本灌区信息化方案以星创易联公司的各类智能设备为基础,通过其产品完成水文、雨情、土壤等多源异构数据的采集,以无线自组网的方式实现数据传输,并在后台管理中心建立信息化软件平台,对数据进行融合处理。系统实现对…...
jmeter自动录制脚本功能
问题排查: 建议用 google浏览器; 重启一下jmeter; 过滤规则重新检查下; 看下代理设置是否正常; 注意:下面的的过滤设置中 用的都是正则表达式的规则。...
十一、工具盒类(MyQQ)(Qt5 GUI系列)
目录 编辑 一、设计需求 二、实现代码 三、代码解析 四、总结 一、设计需求 抽屉效果是软件界面设计中的一种常用形式,可以以一种动态直观的方式在有限大小的界面上扩展出更多的功能。本例要求实现类似 QQ 抽屉效果。 二、实现代码 #include "dialog.…...
postgresql 查询字段 信息
SELECT base.“column_name”, col_description ( t1.oid, t2.attnum ), base.udt_name, COALESCE(character_maximum_length, numeric_precision, datetime_precision), (CASE WHEN ( SELECT t2.attnum ANY ( conkey ) FROM pg_constraint WHERE conrelid t1.oid AND contyp…...
antv/x6_2.0学习使用(四、边)
一、添加边 节点和边都有共同的基类 Cell,除了从 Cell 继承属性外,还支持以下选项。 属性名类型默认值描述sourceTerminalData-源节点或起始点targetTerminalData-目标节点或目标点verticesPoint.PointLike[]-路径点routerRouterData-路由connectorCon…...
C++ stack用法总结
std::stack 是 C 标准模板库(STL)中的容器适配器,它提供了栈(stack)的功能,基于其他序列容器实现。以下是 std::stack 的用法总结: 包含头文件: #include <stack>创建 std::…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
