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

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

在这里插入图片描述

HashMap的数据结构是怎样的?

  • ✔️HashMap的数据结构
    • ✔️ 数组
    • ✔️ 链表


✔️HashMap的数据结构


在Java中,保存数据有两种比较简单的数据结构: 数组和链表(或红黑树)。


HashMapJava 中常用的数据结构,它实现了 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中&#xff0c;保存数据有两种比较简单的数据结构: 数组和链表&#xff08;或红黑树&#xff09;。 HashMap是 Java 中常用的数据结构&#xff0c;它实现了 Map 接口。Has…...

Spring 应用合并之路(一):摸石头过河 | 京东云技术团队

公司在推进降本增效&#xff0c;在尝试多种手段之后&#xff0c;发现应用太多&#xff0c;每个应用都做跨机房容灾部署&#xff0c;则最少需要 4 台机器&#xff08;称为容器更合适&#xff09;。那么&#xff0c;将相近应用做一个合并&#xff0c;减少维护项目&#xff0c;提高…...

Android13配置selinux让system应用可读sys,proc,SN号

system权限应用读sys,proc目录及SN号 Android13预置的system应用&#xff0c;需要读/sys, /proc目录&#xff0c;读(SN)serial number号, 需要修改selinux配置&#xff0c;否则会报avc错&#xff0e; 其修改方法会比Android11复杂一些&#xff0e; 实现 system_app.te中添加…...

防勒索病毒攻击的关键措施

【作者】朱向东 中原银行 高级工程师 在当今数字化时代&#xff0c;勒索病毒成为了企业和个人面临的一项严峻威胁。勒索病毒攻击可以导致数据丢失、系统瘫痪以及经济损失。为了保护自己和组织的利益&#xff0c;采取一系列的防范措施是至关重要的。下面是一些关键的措施&#…...

代表团坐车 - 华为OD统一考试

OD统一考试(B卷) 分值: 100分 题解: Java / Python / C++ 题目描述 某组织举行会议,来了多个代表团同时到达,接待处只有一辆汽车可以同时接待多个代表团,为了提高车辆利用率,请帮接待员计算可以坐满车的接待方案输出方案数量。 约束: 一个团只能上一辆车,并且代表团…...

运用Jmeter进行登录测试

开始了解Jmeter,写篇关于Jmeter的博客做备忘,这里以苏宁易购网站的登录请求为例实战来说明测试计划元件,创建一个 Web 测试计划。 今天简单介绍Jemeter的入门,Jmeter 的安装这边就跳过,直接讲述如何使用JMETER,如何运用Jmeter进行测试。 a.下载jmeter软件 b.安装…...

Docker学习与应用(四)-容器数据卷

1、容器数据卷 1&#xff09;什么是容器数据卷 docker的理念回顾 将应用和环境打包成一个镜像&#xff01; 数据&#xff1f;如果数据都在容器中&#xff0c;那么我们容器删除&#xff0c;数据就会丢失&#xff01;需求&#xff1a;数据可以持久化 MySQL&#xff0c;容器删…...

CentOS 7.6下HTTP隧道代理的安全性考虑

在CentOS 7.6上配置HTTP隧道代理时&#xff0c;安全性是一个不可忽视的重要因素。以下是对HTTP隧道代理安全性的一些关键考虑因素&#xff1a; 1. 加密和数据安全 使用强加密算法&#xff1a;确保您使用的是经过广泛认可和强化的加密算法&#xff0c;如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]

从题目中下载到图片&#xff1a; 考虑图片是png&#xff0c;隐写方式有可能是高宽修改&#xff0c;也可能是色相隐藏&#xff0c;色彩通道位隐藏等等 使用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、常用函数…...

矩阵的乘法

首先矩阵的乘法定义如下&#xff1a; #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自动化测试模块进行网页爬取的示例代码。它通过模拟人的行为在浏览器中操作网页来实现爬取。具体的流程如下&#xff1a; 导入所需的模块&#xff0c;包括Selenium、时间、随机、csv等模块。打开浏览器&#xff0c;创建一个Chrome浏览器实例。设置要爬取…...

灌区信息化方案(什么是现代化灌区,如何一步到位)

一、系统概述 详情&#xff1a;https://www.key-iot.com.cn/ 本灌区信息化方案以星创易联公司的各类智能设备为基础,通过其产品完成水文、雨情、土壤等多源异构数据的采集,以无线自组网的方式实现数据传输,并在后台管理中心建立信息化软件平台,对数据进行融合处理。系统实现对…...

jmeter自动录制脚本功能

问题排查&#xff1a; 建议用 google浏览器&#xff1b; 重启一下jmeter&#xff1b; 过滤规则重新检查下&#xff1b; 看下代理设置是否正常&#xff1b; 注意&#xff1a;下面的的过滤设置中 用的都是正则表达式的规则。...

十一、工具盒类(MyQQ)(Qt5 GUI系列)

目录 ​编辑 一、设计需求 二、实现代码 三、代码解析 四、总结 一、设计需求 抽屉效果是软件界面设计中的一种常用形式&#xff0c;可以以一种动态直观的方式在有限大小的界面上扩展出更多的功能。本例要求实现类似 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&#xff0c;除了从 Cell 继承属性外&#xff0c;还支持以下选项。 属性名类型默认值描述sourceTerminalData-源节点或起始点targetTerminalData-目标节点或目标点verticesPoint.PointLike[]-路径点routerRouterData-路由connectorCon…...

C++ stack用法总结

std::stack 是 C 标准模板库&#xff08;STL&#xff09;中的容器适配器&#xff0c;它提供了栈&#xff08;stack&#xff09;的功能&#xff0c;基于其他序列容器实现。以下是 std::stack 的用法总结&#xff1a; 包含头文件&#xff1a; #include <stack>创建 std::…...

AGV中上位机

在 AGV 系统里&#xff0c;“上位机”一般就是调度/监控系统&#xff0c;负责&#xff1a;全局任务分配、路径规划、交通管制、与 MES/WMS 对接、人机界面等&#xff1b;车上的 PLC / 控制器则作为下位机&#xff0c;负责实时运动控制、传感器采集和执行指令。两者通过以太网 /…...

教育机构搭建AI辅助教学系统时如何通过Taotoken统一接口

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 教育机构搭建AI辅助教学系统时如何通过Taotoken统一接口 构建一个服务于师生的AI辅助教学系统&#xff0c;通常需要集成多种能力&a…...

基于SpringBoot+Vue的CRM客户管理系统毕设

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在构建一个基于Spring Boot与Vue框架的CRM客户管理系统以解决传统客户关系管理中存在的信息孤岛现象与业务流程低效问题。当前企业客户管理普遍面临数据…...

Cadence Allegro自定义快捷键全攻略:从env文件到Skill脚本

1. 项目概述&#xff1a;为什么我们需要自定义快捷键&#xff1f;如果你是一名电子工程师&#xff0c;或者经常使用Cadence Allegro进行PCB设计&#xff0c;那么对软件自带的默认快捷键一定又爱又恨。爱的是&#xff0c;它确实提供了一些基础的操作加速&#xff1b;恨的是&…...

0403开源:第四卷光刻机整机控制与量检测系统(A级 中期集中攻坚)3. 开源纳米级计量检测设备卡点

开源光刻机整机控制与量检测系统&#xff08;A级 中期集中攻坚&#xff09; 3. 开源纳米级计量检测设备卡点&#xff08;全参数开源硬核壁垒拆解喂饭级溯源破局&#xff09; 前置开源声明 本节全程无保留开源光刻量检测底层原理、设备架构、纳米级计量阈值、国内外参数对标、核…...

JetBrains IDE试用期重置工具:30天免费试用无限续杯指南

JetBrains IDE试用期重置工具&#xff1a;30天免费试用无限续杯指南 【免费下载链接】ide-eval-resetter 项目地址: https://gitcode.com/gh_mirrors/id/ide-eval-resetter 你是否遇到过JetBrains IDE试用期到期&#xff0c;却还没准备好购买许可证的困扰&#xff1f;i…...

龙芯平台桥片与GPU技术突破:从硬件瓶颈到均衡体验的实践指南

1. 项目概述&#xff1a;一次迟来的正名“桥片和GPU&#xff0c;已然不是龙芯的短板&#xff01;”——这个标题&#xff0c;对于长期关注国产CPU发展的从业者或爱好者来说&#xff0c;无异于一声响亮的宣告。在过去很长一段时间里&#xff0c;当人们讨论龙芯处理器时&#xff…...

基于MCP协议构建AI智能体记忆系统:mnemo-mcp实战指南

1. 项目概述&#xff1a;一个为AI记忆而生的开源工具最近在折腾AI应用开发&#xff0c;特别是围绕大语言模型&#xff08;LLM&#xff09;构建智能体&#xff08;Agent&#xff09;时&#xff0c;一个绕不开的痛点就是“记忆”。模型本身没有持久化记忆&#xff0c;每次对话都是…...

ZipCPU/dspfilters:轻量级C++ IIR滤波器库的设计原理与嵌入式应用

1. 项目概述&#xff1a;从零开始理解一个数字信号处理滤波器库最近在整理一些嵌入式音频处理的项目&#xff0c;又翻出了ZipCPU/dspfilters这个仓库。这其实是一个在GitHub上存在了相当一段时间的C数字信号处理&#xff08;DSP&#xff09;滤波器库&#xff0c;由ZipCPU&#…...

自指递归系统的唯一最优几何形态:黄金螺旋本征解必然性定理的严格证明(世毫九实验室原创研究)

自指递归系统的唯一最优几何形态&#xff1a;黄金螺旋本征解必然性定理的严格证明&#xff08;世毫九实验室原创研究&#xff09;作者&#xff1a;方见华 单位&#xff1a;世毫九实验室 摘要 针对自然界跨尺度普遍涌现的黄金螺旋结构是否仅为经验巧合或审美投射的长期争议&…...