详解 HashMap 的底层实现原理

作为一名程序员,你可能经常使用 HashMap 这个重要的数据结构,但你对它的底层实现原理可能不够了解。本文将通过图文结合的方式,为你详细解析 HashMap 的底层实现原理,并回答一些常见问题,让你能够更好地理解和应用 HashMap。

1. HashMap 概述

HashMap 是 Java 集合框架中最常用的映射表实现,它提供了键值对的存储和检索功能。底层基于数组和链表(或红黑树)实现,通过哈希算法将键映射到数组的索引位置,以实现快速的插入和查找操作。下面我们来看一下 HashMap 的底层代码流程图:

2. HashMap 的主要方法分析

2.1 put方法

put方法用于将键值对插入到 HashMap 中。让我们看一下put方法的源代码:

首先,通过has(key)方法计算键的哈希码,并调用putVal()方法进行插入操作。下面是putVal()方法的源代码:

putVal()方法的核心是通过哈希码定位桶,然后在桶中进行插入操作。如果桶为空,则直接在桶中插入新节点。如果桶不为空,则遍历链表或红黑树,查找键是否已存在。如果键已存在,则替换对应的值;如果键不存在,则将新节点插入到链表的末尾,并根据链表长度是否达到阈值来决定是否将链表转化为红黑树。最后,更新修改次数和元素数量,并进行必要的操作。

2.2 resize方法

resize方法用于动态扩容 HashMap。当元素数量超过阈值时,HashMap 会自动触发扩容操作。下面是resize方法的源代码:

resize方法的主要功能是创建一个新的数组,并将所有键值对重新计算哈希码和索引位置,然后将它们分配到新的桶中。扩容后的容量是原来的两倍,并根据负载因子重新计算阈值。在转移键值对时,会根据哈希码的高位来确定是保留在原来的位置还是移动到新的位置。如果链表长度超过阈值,则会将链表转化为红黑树以提高查找效率。

2.3 get方法

当我们需要根据键来获取值时,可以使用 HashMap 的get(key)方法。下面讲解下 HashMap 的get方法的原理。

首先,我们传入要查找的键key,然后通过hash(key)方法计算键的哈希码。哈希码被用来确定键在 HashMap 中的桶(bucket)位置。根据哈希码,我们可以确定键在数组中的索引位置。

接下来,我们在确定的索引位置找到对应的桶。如果桶为空,即没有键值对存在,那么返回 null,表示没有找到对应的值。

如果桶不为空,那么可能存在多个键值对,这时我们需要遍历链表或红黑树来找到具有相同键的节点。在遍历过程中,我们会使用键的 equals() 方法来比较传入的键和当前节点的键是否相等。如果找到了相等的键,那么返回对应节点的值。

如果遍历完整个链表或红黑树仍然没有找到相等的键,那么返回 null,表示没有找到对应的值。

整个get()方法的原理就是根据键的哈希码确定索引位置,然后在对应的桶中遍历链表或红黑树,通过 equals() 方法比较键的相等性来找到对应的值。

以下是get方法的伪代码示例:

通过分析上述代码,我们可以看到get方法的实现流程:首先计算键的哈希码,然后根据哈希码找到对应的桶,最后在桶中遍历链表或红黑树,查找具有相同键的节点,如果找到则返回对应的值,否则返回 null。

3. 常见问题分析

3.1为什么哈希码很重要?

哈希码在 HashMap 中扮演着重要的角色。它通过哈希函数将键转换为一个唯一的整数,决定了键值对在数组中的存储位置。如果两个键的哈希码不同,它们会被分配到不同的桶中,提高了查找效率。如果哈希码相同,就会发生哈希冲突,需要进一步处理。

3.2如何处理哈希冲突?

当两个不同的键具有相同的哈希码时,HashMap 会使用链表或红黑树来解决哈希冲突。链表是 JDK 8 之前的解决方案,而红黑树是 JDK 8 之后的优化。HashMap 在桶中通过链表或红黑树结构存储冲突的键值对,以便在查找时能快速定位到正确的键值对。

3.3为什么需要动态扩容?

动态扩容是为了避免哈希冲突过多,提高 HashMap 的性能。当键值对的数量接近数组容量的阈值时,HashMap 会自动触发扩容操作。它创建一个更大的数组,并重新计算每个键的哈希码和索引位置,然后将键值对重新插入到新数组中。这样可以减少桶的数量,降低哈希冲突的概率,提高存储和检索的效率。

3.4如何保证键的唯一性?

HashMap 通过哈希码和链表/红黑树结构来保证键的唯一性。当存储键值对时,如果发现相同的键已经存在于桶中,HashMap 会检查键的 equals() 方法来确定是否是同一个键。如果 equals() 方法返回 true,新的键值对会替换旧的键值对;如果 equals() 方法返回 false,新的键值对会被添加到桶中。这样就确保了 HashMap 中的键是唯一的。

3.5HashMap 和线程安全有关吗?

HashMap 在默认情况下是非线程安全的。多个线程同时对 HashMap 进行插入、删除或查找操作可能会导致不一致的结果。如果在并发环境下使用 HashMap,应考虑使用线程安全的 ConcurrentHashMap 或使用适当的同步机制来保护 HashMap 的访问。

3.6如何选择适当的初始容量和负载因子?

HashMap 的初始容量和负载因子会影响其性能和空间利用率。初始容量是指 HashMap 初始化时的桶数量,默认为 16。负载因子是指 HashMap 在扩容之前允许的平均桶占用比例,默认为 0.75。

选择适当的初始容量和负载因子取决于你的应用需求。如果预计存储的键值对数量较多,可以选择一个较大的初始容量,以减少动态扩容的频率。负载因子较小可以减少哈希冲突的概率,但会增加空间占用。综合考虑,通常可以使用 HashMap 的默认值,并根据实际情况进行调整。

HashMap 是一个强大而灵活的数据结构,合理使用它可以提高程序的性能和效率。通过深入了解 HashMap 的底层实现原理,你可以更好地理解其工作方式,并在实际开发中做出更明智的设计和优化决策。

相关内容拓展:(技术前沿)

近10年间,甚至连传统企业都开始大面积数字化时,我们发现开发内部工具的过程中,大量的页面、场景、组件等在不断重复,这种重复造轮子的工作,浪费工程师的大量时间。

针对这类问题,低代码把某些重复出现的场景、流程,具象化成一个个组件、api、数据库接口,避免了重复造轮子。极大的提高了程序员的生产效率。

推荐一款程序员都应该知道的软件JNPF快速开发平台,采用业内领先的SpringBoot微服务架构、支持SpringCloud模式,完善了平台的扩增基础,满足了系统快速开发、灵活拓展、无缝集成和高性能应用等综合能力;采用前后端分离模式,前端和后端的开发人员可分工合作负责不同板块,省事又便捷。

免费体验官网:https://www.jnpfsoft.com/?csdn。还没有了解低代码这项技术可以赶紧体验学习!


结论

通过以上的源代码分析和常见问题的解答,相信你已经对 HashMap 的底层实现原理有了更深入的理解。HashMap 的底层使用数组和链表(或红黑树)实现,通过哈希算法将键映射到数组的索引位置,以实现快速的插入和查找操作。动态扩容过程会创建一个更大的数组,并重新分配键值对到新的桶中,以提高性能。同时,我们还回答了一些常见问题,希望能帮助你更好地理解和应用 HashMap。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【科普知识】了解电机T型速度曲线和S型速度曲线的区别!

当电机从静止状态启动并加速到额定转速时,其速度变化并非线性的,而是呈现出不同的曲线特征。T型速度曲线和S型速度曲线是两种典型的电机加速曲线类型。那它们之间有什么区别呢?今天,就让我们来深入探讨电机加速曲线的奥秘。 电机速…

uni-ajax网络请求库使用

uni-ajax网络请求库使用 uni-ajax是什么 uni-ajax是基于 Promise 的轻量级 uni-app 网络请求库,具有开箱即用、轻量高效、灵活开发 特点。 下面是安装和使用教程 安装该请求库到项目中 npm install uni-ajax编辑工具类request.js // ajax.js// 引入 uni-ajax 模块 import ajax…

【无标题】一篇文章带你彻底理解Java ArrayList数据结构详解

一篇文章带你彻底理解Java ArrayList数据结构详解 基本概念: ​ **之前创建数组的时候,需要声明提前声明数组的大小,**ArrayList是一个可以动态修改的数组,与普通数组的区别就是没有固定大小的限制,它会动态调整长度…

iphone卡在恢复模式怎么办?修复办法分享!

iPhone 卡在恢复屏幕问题是 iPhone 用户在软件更新或恢复期间的常见问题。如果你也遇到此问题,不要着急,接下来我们将探讨 iPhone 卡在恢复屏幕上的主要原因,以及如何轻松修复它。 iPhone卡在恢复屏幕问题上没有一个特别的原因,但…

【前端实习生备战秋招】—HTML 和 CSS面试题总结(一)

【前端实习生备战秋招】—HTML 和 CSS面试题总结(一) 1. 你做的页面在哪些流览器测试过?这些浏览器的内核分别是什么? IE:trident内核 Firefox:gecko内核 Safari:webkit内核 Opera:以前是presto内核,Opera现已改用Goo…

网络安全设备-等保一体机

本文为作者学习文章,按作者习惯写成,如有错误或需要追加内容请留言(不喜勿喷) 本文为追加文章,后期慢慢追加 等保一体机的功能 等保一体机产品主要依赖于其丰富的安全网元(安全网元包括:防火…

Spark写PGSQL分区表

这里写目录标题 需求碰到的问题格式问题分区问题(重点) 解决完整代码效果 需求 spark程序计算后的数据需要往PGSQL中的分区表进行写入。 碰到的问题 格式问题 使用了字符串格式,导致插入报错。 val frame df.withColumn("insert_t…

Go语言开发者的Apache Arrow使用指南:读写Parquet文件

Apache Arrow是一种开放的、与语言无关的列式内存格式,在本系列文章[1]的前几篇中,我们都聚焦于内存表示[2]与内存操作[3]。 但对于一个数据库系统或大数据分析平台来说,数据不能也无法一直放在内存中,虽说目前内存很大也足够便宜…

【数据挖掘竞赛】——科大讯飞:锂离子电池生产参数调控及生产温度预测挑战赛

🤵‍♂️ 个人主页:@Lingxw_w的个人主页 ✍🏻作者简介:计算机科学与技术研究生在读 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞👍🏻 收藏 📂加关注+ ​ 【科大讯飞】报名链接:https://challenge.xfyun.cn?invitaC…

【ChatGLM_02】LangChain知识库+Lora微调chatglm2-6b模型+提示词Prompt的使用原则

经验沉淀 1 知识库1.1 Langchain知识库的主要功能(1) 配置知识库(2) 文档数据测试(3) 知识库测试模式(4) 模型配置 2 微调2.1 微调模型的概念2.2 微调模型的方法和步骤(1) 基于ptuning v2 的微调(2) 基于lora的微调 3 提示词3.1 Prompts的定义及原则(1) Prompts是什么&#xf…

CentOS7忘记密码如何重置

忘记密码重置步骤: 1、重启系统,当系统进入引导界面时,按e键。就可以编辑引导选项,在引导选中加入参数rd.break,如图所示: 2、编辑完引导选项后,按Ctrlx组合键引导系统进入紧急模式&#xff0c…

关于CORS的笔记

CORS目录 一、SpringBoot 跨域设置二、CORS(1)总结的图如下(2)简单请求满足的条件(3)响应头(4)请求头(5)使用XMLHttpRequest进行跨域访问1. Access-Control-A…

DEVICENET转ETHERNET/IP网关devicenet协议

捷米JM-EIP-DNT,你听说过吗?这是一款自主研发的ETHERNET/IP从站功能的通讯网关,它能够连接DEVICENET总线和ETHERNET/IP网络,从而解决生产管理系统中协议不同造成的数据交换互通问题。 这款产品在工业自动化领域可谓是一大利器&…

springboot()—— swagger

零、一张图读懂swagger 懂了,这玩意就是用swagger搞出来的! 就是一个后端开发自测的东西嘛! 一、概念 存在即合理,我们看一下swagger诞生的原因:在前后端分离的架构中,前端新增一个字段,后端就…

【FAQ】在Linux中使用curl访问EasyCVR,返回报错Unauthorized的原因排查

EasyCVR可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力,比如:视…

Vue-函数式组件

最近在开发项目的时候&#xff0c;定制了一个公司内部样式的Modal模态框组件。 Modal组件伪代码 <!-- Modal/index.vue--> <template><div class"modal-container" id"modalContainer"><!-- Modal Content --><div class&quo…

45.ubuntu Linux系统安装教程

目录 一、安装Vmware 二、Linux系统的安装 今天开始了新的学习&#xff0c;Linux,下面是今天学习的内容。 一、安装Vmware 这里是在 Vmware 虚拟机中安装 linux 系统&#xff0c;所以需要先安装 vmware 软件&#xff0c;然 后再安装 Linux 系统。 所需安装文件&#xff1a;…

Java-接口

目录 1.接口的概念 2.语法规则 3.接口使用 4.接口特性 5.实现多个接口 6.接口间的继承 7.接口使用实例 1.接口的概念 电脑的USB口上&#xff0c;可以插&#xff1a;U盘、鼠标、键盘等所有符合USB协议的设备&#xff1b;数据线的type-c口上&#xff0c;可以插手机&#xf…

Spring源码篇(九)自动配置扫描class的原理

文章目录 前言ClassLoader如何加载jar包里的class自动配置扫描class的原理spring中的加载方式源码总结 前言 spring是怎样通过ComponentScan&#xff0c;或者自动配置扫描到了依赖包里class的&#xff1f; ClassLoader 这里涉及到了class Loader的机制&#xff0c;有些复杂&…

Unity Image(RawImage) 实现按轴心放大缩小,序列化存储轴心信息,实现编译器窗口保存轴心

工作时分配给我的要实现的功能&#xff0c;写的时候遇到挺多的坑的&#xff0c;在此记录一下 效果 放大缩小的效果 2.编译器扩展窗口记录 实现点 1.Json序列化存储图片轴心位置, 放大倍率&#xff0c;放大所需要的事件 2.用了编译器扩展工具便于保存轴心信息坑点 1.Imag…