java-HashMap 的底层原理

HashMap 是 Java 中最常用的映射数据结构,它存储键值对(key-value pairs),并允许使用任何非空对象作为键或值。HashMap 的底层原理主要依赖于数组和链表(或红黑树)来实现键值对的存储和检索。

以下是 HashMap 的主要底层原理:

  1. 数组:HashMap 内部维护了一个数组,这个数组被称为“桶”(buckets)。每个桶用来存储一个或多个键值对。当创建 HashMap 时,如果没有指定初始容量,它会使用一个默认的容量大小(通常是 16)。

  2. 哈希函数:当向 HashMap 插入一个键值对时,会使用哈希函数来计算键的哈希码(hash code),然后使用这个哈希码来确定键值对应该存储在数组的哪个桶中。

  3. 链表:如果两个不同的键产生了相同的哈希码或者不同的哈希码映射到了同一个桶,这时会发生“哈希冲突”。HashMap 通过在数组中使用链表来解决哈希冲突。每个桶中可以存储多个键值对,它们通过链表连接起来。

  4. 红黑树:在 Java 8 之后,为了提高性能,当链表中的元素数量超过一定阈值(默认为 8)时,链表会被转换为红黑树。红黑树是一种自平衡的二叉搜索树,它可以更高效地处理大量的哈希冲突。

  5. 扩容:当 HashMap 中的元素数量达到容量和负载因子(load factor)的乘积时,HashMap 会进行扩容操作,即创建一个新的更大的数组,并将所有现有的键值对重新哈希到新数组中。这个过程通常称为“rehashing”。

  6. 迭代器:HashMap 提供了迭代器(Iterator),用于遍历映射中的所有键值对。迭代器是 fail-fast 的,这意味着如果在迭代过程中映射结构被修改,迭代器会立即抛出 ConcurrentModificationException

  7. 容量和大小:HashMap 中有两个重要的概念:容量(capacity)和大小(size)。容量指的是数组的长度,而大小指的是映射中实际包含的键值对的数量。

    下面是一个更准确的简化的 HashMap 内部实现示例:

    public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, java.io.Serializable
    {private static final long serialVersionUID = 3624988207751029749L;// 默认容量大小private static final int DEFAULT_INITIAL_CAPACITY = 16;// 最大容量,必须是2的幂private static final int MAXIMUM_CAPACITY = 1 << 30;// 默认负载因子private static final float DEFAULT_LOAD_FACTOR = 0.75f;// 桶数组transient Node<K,V>[] table;// 元素数量transient int size;// 扩容阈值,容量*负载因子transient int threshold;// 负载因子final float loadFactor;// 节点类,用于存储键值对static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}// 实现Map.Entry的方法K getKey() {return key;}V getValue() {return value;}V setValue(V value) {V oldValue = this.value;this.value = value;return oldValue;}// 实现Node的方法// ...}// 构造函数public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Initial Capacity: " + initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal Load Factor: " + loadFactor);this.loadFactor = loadFactor;this.threshold = (int)(initialCapacity * loadFactor);this.table = new Node[initialCapacity];}// 默认构造函数public HashMap() {this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);}// 其他方法,如put(K, V),get(K),remove(K)等// ...
    }
    

    在这个简化的示例中,HashMap 使用了一个名为 Node 的内部类来表示映射中的元素。每个 Node 对象包含一个键、一个值、一个哈希码和一个指向下一个 Node 的指针。在实际的字节码文件中,Node 类会包含更多的方法,例如 toString() 和 hashCode(),这些方法用于支持 HashMap 的操作。

    HashMap 的主要操作,如 put(K, V)get(K) 和 remove(K),都是通过计算键的哈希码来定位到相应的桶,然后在桶中的链表(或红黑树)中查找、插入或删除节点。

    扩容操作会在数组的元素数量达到阈值(threshold)时触发,此时会创建一个新的更大的数组,并将所有现有的元素重新哈希到新数组中。这个过程中,原有的链表可能会被转换为红黑树,以提高在大量元素情况下的性能。

    HashMap 的迭代器也是 fail-fast 的,这意味着在迭代过程中如果映射结构被修改,迭代器会立即抛出 ConcurrentModificationException。继续解释 `HashMap` 的底层原理,我们来看一下几个关键的方法:
    1. `put(K, V)`:这是向 `HashMap` 添加一个键值对的方法。它首先计算键的哈希码,然后找到对应的桶。如果桶中没有元素,或者找到了具有相同哈希码的键(发生了哈希冲突),则将新的键值对插入到链表或红黑树中。如果替换了现有的键值对,则会返回被替换的值。
    2. `get(K)`:这是获取 `HashMap` 中指定键的值的方法。它通过计算键的哈希码找到对应的桶,然后在链表或红黑树中查找具有相应键的节点。如果找到了键,则返回对应的值;如果没有找到,则返回 `null`。
    3. `remove(K)`:这是从 `HashMap` 中删除指定键的方法。它通过计算键的哈希码找到对应的桶,然后在链表或红黑树中删除具有相应键的节点。如果删除了节点,则返回被删除节点的值;如果没有找到键,则返回 `null`。
    4. `size()`:这是返回 `HashMap` 中元素数量的方法。它不需要遍历整个桶数组,因为每个桶中的节点数量可以通过数组的长度减去已使用的位置来快速计算。
    5. `resize()`:这是扩容方法,它在数组的元素数量达到阈值时被调用。它创建一个新的更大的数组,然后将所有现有的元素重新哈希到新数组中。这个过程中,原有的链表可能会被转换为红黑树。
    `HashMap` 的性能取决于哈希函数的质量、链表和红黑树的实现效率以及扩容操作的频率。在理想情况下,哈希冲突应该很少发生,这样可以保持高效的查找、插入和删除操作。然而,在实际应用中,由于键的哈希码可能相互碰撞,哈希冲突是难以避免的。`HashMap` 通过链表和红黑树来处理这些冲突,这样可以保持较高的平均时间复杂度。
    需要注意的是,`HashMap` 并不是线程安全的。如果在多线程环境中使用 `HashMap`,并且有线程安全的需求,可以选择使用 `ConcurrentHashMap` 或其他线程安全的替代品。

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

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

相关文章

嵌入式必会的几条ARM汇编指令

就这几条混个面熟就行 读内存指令&#xff1a;LDR&#xff0c;即Load之意写内存指令&#xff1a;STR&#xff0c;即Store之意加减指令&#xff1a;ADD、SUB跳转&#xff1a;BL&#xff0c;即Branch And Link入栈指令&#xff1a;PUSH出栈指令&#xff1a;POP 此处是学习韦老师…

在win11系统上安装启动Hyper-V

Hyper-V 是微软公司开发的一种虚拟化技术&#xff0c;它允许一台物理计算机运行多个操作系统和应用程序&#xff0c;从而提供更好的资源利用率和系统灵活性。 win系统的linux子系统开启、android studio的虚拟环境都需要这个东西&#xff0c;而在初始的win11系统上可能没有这个…

Nvidia/算能 +FPGA+AI大算力边缘计算盒子:电力巡检智能机器人

聚焦数字经济与双碳经济赛道&#xff0c;专注于提供集中式新能源场站与分布式综合能源数智化整体解决方案&#xff0c;坚持以场站数字化、综合能源数字化双轮驱动发展。依靠专业化人才队伍与丰富的实证基地研究经验&#xff0c;打造成熟、先进的数智新能源研发平台。 在集中式新…

【面经总结】Java基础 - 面向对象

面向对象 什么是面向对象 https://www.bilibili.com/video/BV1wZ4y1B7A7 以对象为核心。 类是对象创建的模板&#xff0c;对象是类的实例。 面向对象三大特性是什么 封装&#xff1a;类的创建者将类具体实现隐藏&#xff0c;使用者只需要使用外部接口即可继承&#xff1a;类…

常用sql

–page– jpa里面的&#xff0c;需要-1Pageable pageable PageRequest.of(pageNumber - 1, pageSize, Sort.by(“classId”).descending()); RepositoryImpl里面的pageable不需要-1 queryListForMySql(sql, pageable, BeanPropertyRowMapper.newInstance(DingStudentDto.class…

Docker Swarm集群部署管理

Docker Swarm集群管理 文章目录 Docker Swarm集群管理资源列表基础环境一、安装Docker二、部署Docker Swarm集群2.1、创建Docker Swarm集群2.2、添加Worker节点到Swarm集群2.3、查看Swarm集群中Node节点的详细状态信息 三、Docker Swarm管理3.1、案例概述3.2、Docker Swarm中的…

Unity Mirror VR联机开发 房间篇

一、需求 在联机时通常有加入房间这个步骤&#xff0c;在mirror示例中也有相应的案例&#xff0c;但是那个比较复杂&#xff0c;我们做教育科普类不需要如此复杂&#xff0c;傻瓜式操作基本就可以了&#xff0c;所以我简化了步骤&#xff0c;省略了点击准备按钮这一步骤&#…

【Web世界探险家】3. CSS美学(二)文本样式

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

2024-06-10 问AI: 在大语言模型中,ReAct Agents的理论基础是什么

文心一言 在大语言模型中&#xff0c;ReAct Agents的理论基础主要基于以下几个关键点&#xff1a; 推理与行为的结合&#xff1a;ReAct框架的核心思想是将大型语言模型的推理能力和行为能力相结合。这种结合使得模型能够以交错的方式生成推理步骤和特定于任务的操作&#xff0c…

基于深度学习的中文语音识别模型(支持wav、mp4、m4a等所有格式音频上传)【已开源】

基于深度学习的中文语音识别模型&#xff08;支持wav、mp4、m4a等所有格式音频上传&#xff09; 前言 该开源项目旨在提供一个能够自动检测并识别中文语音的模型&#xff0c;支持wav、mp4、m4a等格式的音频文件上传。无论是从录音设备中获取的wav文件&#xff0c;还是从视频中…

CentOS Python 2.7 离线安装 Requests 库保姆级教程

在内网或无网络连接的环境中&#xff0c;Python 开发者经常需要离线安装第三方库。本文将详细介绍如何在 CentOS 系统上&#xff0c;使用 Python 2.7 版本离线安装 Requests 库。Requests 是一个简单易用的 HTTP 库&#xff0c;用于发送各种 HTTP 请求。 前提条件 CentOS 系统…

C++进阶教程

一、引言 C是一种高效、强大且灵活的编程语言&#xff0c;广泛应用于系统软件开发、游戏开发、科学计算等领域。对于已经掌握C基础知识的开发者来说&#xff0c;进阶学习C将帮助他们更深入地理解这门语言&#xff0c;并提升编程能力。本教程将介绍C中的一些高级特性和技术&…

外部排序快速入门详解:基本原理,败者树,置换-选择排序,最佳归并树

文章目录 外部排序1.最基本的外部排序原理2.外部排序的优化2.1 败者树优化方法2.2 置换-选择排序优化方法2.3 最佳归并树 外部排序 为什么要学习外部排序&#xff1f; 答&#xff1a; 在处理数据的过程中&#xff0c;我们需要把磁盘(外存&#xff09;中存储的数据拿到内存中处理…

ue5创建地图瓦片

先在虚幻商城下载免费的paperzd插件&#xff0c;并启用。 导入资源后&#xff0c;先通过应用paper2d纹理资源&#xff0c;将去掉导入ue时产生的边缘模糊&#xff0c;再点击下面的创建瓦片集&#xff0c; 打开瓦片集&#xff0c;发现选中不对&#xff0c; 改变瓦片大小为16*…

ChatGPT对话基本原则和玩法

一、使用三个准备 1.1 认知上 超级学霸&#xff0c;几乎所有的工作/生活场景&#xff0c;都可以找它帮忙 ChatGPT作为一个人工智能语言模型&#xff0c;具有强大的知识储备和处理能力。这意味着在许多工作和生活场景中&#xff0c;你都可以向它请教问题或寻求帮助。无论是科…

Virustotal查询恶意进程

1、使用netstat查看可疑进程 执行ls -al /proc/$PID/exe确认可疑进程对应的文件&#xff1b;若文件未被删除&#xff0c;则直接上传文件到Virustotal进行检测&#xff0c;或者计算出文件对应的md5&#xff0c;使用md5去Virustotal进行查询&#xff1b;若文件已被删除&#xff0…

Python第二语言(七、Python模块)

目录 1. 什么是模块 2. 基本语法 2.1 模块的导入方式 2.2 基本语法 import 模块名 2.3 基本语法 from 模块名 import 功能名 2.4 基本语法as 别名 3. 自定义模块 4. 调用自定义模块时&#xff0c;如何让其模块中的函数不被调用&#xff08;__name__&#xff09; 5. 调…

java面试题:hashCode的作用

在Java集合中&#xff0c;hashCode起着至关重要的作用&#xff0c;特别是在基于哈希的集合类如HashMap、HashSet和Hashtable中。以下是hashCode在集合中的主要作用&#xff1a; 快速查找和定位&#xff1a; hashCode被用作确定对象在哈希表中存储位置的索引&#xff08;或称为“…

基于SSM+Jsp的家用电器销售网站

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

微信小程序获取 OpenId 和 UnionId

文章目录 1.什么是 OpenId 和 UnionId&#xff1f;2.获取 OpenId 和 UnionId 的办法3.备注 前言&#xff1a;最近开发小程序&#xff0c;需要通过用户登录的唯一值存储数据&#xff0c;查看手册发现要使用的是 “OpenId” 和 “UnionId” 1.什么是 OpenId 和 UnionId&#xff1…