在 Java 键值对(Key-Value)集合中,HashMap 是使用频率最高的实现类之一,凭借高效的查找、插入性能,成为日常开发的 “利器”。本文将从 HashMap 的底层原理、核心特点、常用方法到遍历方式、使用注意事项,进行系统性梳理,帮助快速掌握其核心逻辑与实战技巧。

一、HashMap 核心认知:底层原理与特点

HashMap 本质是哈希表(数组 + 链表 / 红黑树) 实现的键值对存储结构,核心目标是通过 “哈希算法” 快速定位元素,平衡查询与增删效率。其核心特点如下:

1. 底层存储结构(JDK 1.8+)

  • 基础结构:数组(称为 “哈希桶”)+ 链表 + 红黑树。
    • 数组:每个元素是一个 “链表头节点”,通过 key 的哈希值计算数组索引(index = (数组长度 - 1) & 哈希值),实现快速定位。
    • 链表:当多个 key 计算出相同索引(哈希冲突)时,元素以链表形式存储在对应桶中。
    • 红黑树:当链表长度超过 8 且数组长度 ≥ 64 时,链表会转为红黑树,将查询时间复杂度从 O (n) 优化为 O (log n)(避免链表过长导致性能下降)。
  • 示意图简化理解
数组索引 0:[Node(key1, val1)] → [Node(key2, val2)]  // 链表(长度<8)
数组索引 1:[TreeNode(key3, val3)] → 红黑树结构       // 红黑树(长度≥8)
数组索引 2:null
...

2. 核心特点

  1. 键值对规则
    • Key 唯一:若添加重复 Key,新 Value 会覆盖旧 Value(put() 方法返回旧 Value)。
    • Value 可重复:不同 Key 可对应相同 Value。
    • 允许 null:Key 最多允许 1 个 null(重复添加 null Key 会覆盖),Value 可多个 null。
  2. 无序性:存储顺序与插入顺序无关(底层按哈希值排序,非插入顺序)。
  3. 线程不安全:非同步设计,多线程同时读写可能出现数据异常(如 ConcurrentModificationException),需手动处理线程安全(如 Collections.synchronizedMap() 或 ConcurrentHashMap)。
  4. 自动扩容
    • 默认初始容量:16(数组长度,必须是 2 的幂,确保哈希计算均匀)。
    • 负载因子:默认 0.75(当元素数量 ≥ 容量 × 负载因子时触发扩容)。
    • 扩容规则:新容量 = 旧容量 × 2,同时重新计算所有元素的哈希索引(“rehash”),会消耗一定性能。
  5. 高效性能
    • 理想情况下,插入、查询、删除的时间复杂度均为 O(1)(直接通过哈希值定位桶)。
    • 哈希冲突较少时,性能接近理想值;冲突严重(链表过长)时,性能会下降(红黑树优化可缓解此问题)。

二、HashMap 常用方法

HashMap 提供了丰富的方法操作键值对,以下是开发中最常用的方法,均附完整示例代码,可直接复制运行。

1. 基础操作:添加、获取、删除

(1)添加键值对(put ())
  • V put(K key, V value):添加键值对,若 Key 已存在则覆盖 Value,返回旧 Value(若 Key 不存在则返回 null)。
  • void putAll(Map<? extends K, ? extends V> m):将另一个同类型 Map 的所有键值对添加到当前 HashMap 中(重复 Key 会被覆盖)。
import java.util.HashMap;public class HashMapPutDemo {public static void main(String[] args) {// 1. 单个键值对添加HashMap<String, Integer> scoreMap = new HashMap<>();Integer oldMathScore = scoreMap.put("数学", 90); // Key 不存在,返回 nullSystem.out.println("旧数学成绩:" + oldMathScore); // 输出:nullscoreMap.put("语文", 85);scoreMap.put("英语", 95);System.out.println("添加后:" + scoreMap); // 输出:{数学=90, 语文=85, 英语=95}// 重复 Key 覆盖:数学成绩从 90 改为 98Integer updatedOldScore = scoreMap.put("数学", 98);System.out.println("被覆盖的旧数学成绩:" + updatedOldScore); // 输出:90System.out.println("覆盖后:" + scoreMap); // 输出:{数学=98, 语文=85, 英语=95}// 2. 批量添加(putAll())HashMap<String, Integer> extraScoreMap = new HashMap<>();extraScoreMap.put("物理", 88);extraScoreMap.put("化学", 92);scoreMap.putAll(extraScoreMap);System.out.println("批量添加后:" + scoreMap); // 输出:{数学=98, 语文=85, 英语=95, 物理=88, 化学=92}}
}
(2)获取值与判断存在(get ()、containsKey ()、containsValue ())
  • V get(Object key):根据 Key 获取 Value,若 Key 不存在则返回 null(注意:若 Value 本身是 null,需用 containsKey() 区分 “Key 不存在” 和 “Value 为 null”)。
  • boolean containsKey(Object key):判断 HashMap 是否包含指定 Key,返回布尔值。
  • boolean containsValue(Object value):判断 HashMap 是否包含指定 Value,返回布尔值。
public class HashMapGetContainsDemo {public static void main(String[] args) {HashMap<String, Integer> scoreMap = new HashMap<>();scoreMap.put("数学", 98);scoreMap.put("语文", 85);scoreMap.put("生物", null); // Value 为 null// 1. 根据 Key 获取 ValueInteger mathScore = scoreMap.get("数学");Integer historyScore = scoreMap.get("历史"); // Key 不存在Integer bioScore = scoreMap.get("生物"); // Value 本身是 nullSystem.out.println("数学成绩:" + mathScore); // 输出:98System.out.println("历史成绩(Key 不存在):" + historyScore); // 输出:nullSystem.out.println("生物成绩(Value 为 null):" + bioScore); // 输出:null// 2. 判断 Key 是否存在(区分“Key 不存在”和“Value 为 null”)boolean hasBioKey = scoreMap.containsKey("生物");boolean hasHistoryKey = scoreMap.containsKey("历史");System.out.println("是否包含 Key '生物':" + hasBioKey); // 输出:trueSystem.out.println("是否包含 Key '历史':" + hasHistoryKey); // 输出:false// 3. 判断 Value 是否存在boolean has98 = scoreMap.containsValue(98);boolean has100 = scoreMap.containsValue(100);System.out.println("是否包含 Value 98:" + has98); // 输出:trueSystem.out.println("是否包含 Value 100:" + has100); // 输出:false}
}
(3)删除键值对(remove ())
  • V remove(Object key):根据 Key 删除键值对,返回被删除的 Value(若 Key 不存在则返回 null)。
public class HashMapRemoveDemo {public static void main(String[] args) {HashMap<String, Integer> scoreMap = new HashMap<>();scoreMap.put("数学", 98);scoreMap.put("语文", 85);scoreMap.put("英语", 95);// 删除 Key 为“语文”的键值对Integer removedChineseScore = scoreMap.remove("语文");System.out.println("被删除的语文成绩:" + removedChineseScore); // 输出:85System.out.println("删除后:" + scoreMap); // 输出:{数学=98, 英语=95}// 删除不存在的 KeyInteger removedHistoryScore = scoreMap.remove("历史");System.out.println("删除不存在的 Key 返回值:" + removedHistoryScore); // 输出:null}
}

2. 进阶操作:修改、清空、判断空否

(1)修改 Value(replace ())
  • V replace(K key, V value):仅当 Key 存在时,用新 Value 替换旧 Value,返回旧 Value(若 Key 不存在则返回 null,区别于 put()put() 会新增不存在的 Key)。
public class HashMapReplaceDemo {public static void main(String[] args) {HashMap<String, Integer> scoreMap = new HashMap<>();scoreMap.put("数学", 98);scoreMap.put("英语", 95);// 修改存在的 Key(英语成绩从 95 改为 97)Integer oldEnglishScore = scoreMap.replace("英语", 97);System.out.println("被修改的旧英语成绩:" + oldEnglishScore); // 输出:95System.out.println("修改后:" + scoreMap); // 输出:{数学=98, 英语=97}// 修改不存在的 Key(不会新增,返回 null)Integer oldHistoryScore = scoreMap.replace("历史", 80);System.out.println("修改不存在的 Key 返回值:" + oldHistoryScore); // 输出:nullSystem.out.println("修改后集合:" + scoreMap); // 输出:{数学=98, 英语=97}(无变化)}
}
(2)清空与判断空否(clear ()、isEmpty ()、size ())
  • void clear():清空 HashMap 中所有键值对(集合变为空,对象本身仍存在)。
  • boolean isEmpty():判断 HashMap 是否为空(元素个数为 0),返回布尔值。
  • int size():返回 HashMap 中键值对的实际个数(区别于 “容量”)。
public class HashMapClearEmptySizeDemo {public static void main(String[] args) {HashMap<String, Integer> scoreMap = new HashMap<>();scoreMap.put("数学", 98);scoreMap.put("英语", 97);// 1. 获取集合大小System.out.println("初始元素个数:" + scoreMap.size()); // 输出:2// 2. 判断是否为空System.out.println("初始是否为空:" + scoreMap.isEmpty()); // 输出:false// 3. 清空集合scoreMap.clear();System.out.println("清空后元素个数:" + scoreMap.size()); // 输出:0System.out.println("清空后是否为空:" + scoreMap.isEmpty()); // 输出:true}
}

3. HashMap 三种核心遍历方式

HashMap 存储的是 “键值对(Entry)”,遍历需围绕 “Key 集合”“Value 集合”“Entry 集合” 展开,三种常用方式如下:

(1)遍历 Key 集合,再获取 Value(keySet ())

通过 keySet() 获取所有 Key 的集合,遍历 Key 后用 get(key) 获取对应 Value,适合仅需 Key 或需通过 Key 处理 Value 的场景。

public class HashMapKeySetDemo {public static void main(String[] args) {HashMap<String, Integer> scoreMap = new HashMap<>();scoreMap.put("数学", 98);scoreMap.put("语文", 85);scoreMap.put("英语", 97);// 遍历 Key 集合for (String subject : scoreMap.keySet()) {Integer score = scoreMap.get(subject);System.out.println(subject + ":" + score);}// 输出(顺序不固定):// 数学:98// 语文:85// 英语:97}
}
(2)直接遍历 Entry 集合(entrySet ())

通过 entrySet() 获取所有键值对(Map.Entry<K, V>)的集合,直接获取 Key 和 Value,效率最高(无需二次 get(key) 查询),是开发首选。

public class HashMapEntrySetDemo {public static void main(String[] args) {HashMap<String, Integer> scoreMap = new HashMap<>();scoreMap.put("数学", 98);scoreMap.put("语文", 85);scoreMap.put("英语", 97);// 遍历 Entry 集合(推荐)for (HashMap.Entry<String, Integer> entry : scoreMap.entrySet()) {String subject = entry.getKey(); // 获取 KeyInteger score = entry.getValue(); // 获取 ValueSystem.out.println(subject + ":" + score);}// 输出(顺序不固定):// 数学:98// 语文:85// 英语:97}
}
(3)遍历 Value 集合(values ())

通过 values() 获取所有 Value 的集合,仅遍历 Value,适合无需 Key、仅需处理 Value 的场景(无法通过 Value 反向获取 Key)。

public class HashMapValuesDemo {public static void main(String[] args) {HashMap<String, Integer> scoreMap = new HashMap<>();scoreMap.put("数学", 98);scoreMap.put("语文", 85);scoreMap.put("英语", 97);// 遍历 Value 集合System.out.println("所有成绩:");for (Integer score : scoreMap.values()) {System.out.println(score);}// 输出(顺序不固定):// 98// 85// 97}
}

三、HashMap 使用注意事项

  1. Key 的选择原则

    • Key 必须重写 hashCode() 和 equals() 方法(否则无法正确判断 Key 唯一性,导致哈希冲突无法解决)。
    • 推荐使用不可变类作为 Key(如 StringInteger):若 Key 是可变对象,修改后哈希值变化,会导致无法通过原 Key 获取 Value。
    • 示例:若用 User 类作为 Key,需手动重写方法:
class User {private String id;// 重写 hashCode() 和 equals()@Overridepublic int hashCode() { return id.hashCode(); }@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User user = (User) o;return Objects.equals(id, user.id);}
}
  1. null 键值的注意事项

    • Key 最多 1 个 null,重复添加会覆盖;Value 可多个 null。
    • 用 get(key) 获取 Value 时,若返回 null,需通过 containsKey(key) 确认是 “Key 不存在” 还是 “Value 为 null”。
  2. 线程安全问题

    • 单线程环境:直接使用 HashMap 即可。
    • 多线程环境:
      • 若需弱一致性:使用 Collections.synchronizedMap(new HashMap<>())(对整个 HashMap 加锁,性能较低)。
      • 若需高性能:优先使用 ConcurrentHashMap(JDK 1.8+ 采用分段锁,性能优于同步 HashMap)。
  3. 性能优化技巧

    • 初始容量指定:若已知元素数量,创建时指定初始容量(如 new HashMap<>(100)),避免频繁扩容(扩容需 rehash,消耗性能)。
    • 负载因子调整:默认 0.75 是 “性能与空间” 的平衡,若内存充足可降低(如 0.5,减少哈希冲突),若内存紧张可提高(如 0.8,减少数组占用空间)。
    • 避免哈希冲突:合理重写 Key 的 hashCode() 方法,尽量让哈希值均匀分布,减少链表 / 红黑树的长度。

与 TreeMap/Hashtable 的区别(避免混淆)

特性HashMapTreeMapHashtable(不推荐)
排序无序(按哈希值)有序(Key 自然排序 / 自定义排序)无序
线程安全非线程安全非线程安全线程安全(全方法同步)
null 允许Key 1 个 null,Value 多个 null不允许 null不允许 null
底层结构数组 + 链表 / 红黑树红黑树数组 + 链表
适用场景通用高效查询需要有序键值对遗留多线程场景(已被 ConcurrentHashMap 替代)

四、总结

HashMap 是 Java 键值对集合的核心实现,核心优势在于 “哈希表” 带来的 O (1) 高效性能,适合大多数无需有序、单线程的键值对存储场景。掌握其底层结构(数组 + 链表 / 红黑树)、常用方法(put/get/remove/ 遍历)及使用注意事项(Key 重写方法、线程安全、性能优化),就能在开发中灵活应对各类场景。

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

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

相关文章

[系统架构设计师]安全架构设计理论与实践(十八)

[系统架构设计师]安全架构设计理论与实践&#xff08;十八&#xff09; 一.信息安全面临的威胁 1.信息系统安全威胁的来源 物理环境&#xff0c;通信链路&#xff0c;网络系统&#xff0c;操作系统&#xff0c;应用系统&#xff0c;管理系统 2.网络与信息安全风险类别 风险类别…

AI适老服务暖人心:AI适老机顶盒破数字鸿沟、毫米波雷达护独居安全,银发生活新保障

银发经济领域长期受限于 “专业照护资源稀缺”“老年人数字适应能力弱”“独居老人安全隐患多” 的困境&#xff0c;而 AI 技术的适老化改造&#xff0c;正让银发服务从 “被动保障” 转向 “主动关怀”&#xff0c;既能帮老年人跨越数字鸿沟&#xff0c;又能为独居老人筑起安全…

Linux应用软件编程---网络编程1(目的、网络协议、网络配置、UDP编程流程)

Linux下的网络编程一、目的不同主机&#xff0c;进程间通信。二、解决的问题1. 主机与主机之间物理层面必须互联互通。 2. 进程与进程在软件层面必须互联互通。物理层面的互联互通流程图如下&#xff1a;其中&#xff1a;IP地址&#xff1a;计算机的软件地址&#xff0c;用来标…

常见开源协议详解:哪些行为被允许?哪些被限制?

常见开源协议详解&#xff1a;哪些行为被允许&#xff1f;哪些被限制&#xff1f; 开源世界的魅力在于共享与合作&#xff0c;但不同的开源协议对分发、修改、再发布以及宣传/推广有不同的要求和限制。很多开发者在 fork 项目、改 README、放到自己仓库并在自媒体传播 时&…

服务器硬盘进行分区和挂载

查看服务器上的硬盘&#xff1a;lsblk -d -o NAME,SIZE,MODEL可以看到我的硬盘是除了vda系统盘以外&#xff0c;还有个vdb。我们查看一下分区&#xff1a;lsblk可以看到&#xff1a;vdb 1T disk &#xff08;底下没有分区&#xff0c;也没有挂载&#xff09;我们想要用起来这…

【C初阶】数据在内存中的存储

目录 1. 整数在内存中的存储 2. 大小端字节序 2.1 什么是大小端&#xff1f; 2.2 为什么有大小端&#xff1f; 2.3 练习 2.3.1 练习1 2.3.2 练习2 2.3.3 练习3 2.3.4 练习4 2.3.5 练习5 2.3.6 练习6 3. 浮点数在内存中的存储 3.1 浮点数存储的过程 3.2 浮点数的取…

AI 自动化编程 trae 体验2 帮我分析一个项目

总结&#xff1a; 接手一个项目可以让trae 帮忙分析 上次讲到trae在处理组件引入的时候&#xff0c;经常会碰到版本问题&#xff0c;分析引入了互联网上非本版本或者有bug的代码。主要依赖互联网的资源库。 但是分析一个项目应该是没问题。 这次表现非常好&#xff0c;接手一个…

VMware虚拟机中CentOS 7 报错 ping: www.xxx.com: Name or service not known

1:主要原因是网络配置的问题 2:其实就是下面三张图片中的,物理机虚拟网卡 vmware8 和虚拟机网络编辑器&#xff0c;如果设置静态IP 就是这三个地方的问题最简单的解决办法第一步&#xff1a;还原虚拟机网络点击确认后 ** 第二步给自己的虚拟机设置网络连接方式 选择NAT模式连接…

Java面试-自动装箱与拆箱机制解析

&#x1f44b; 欢迎阅读《Java面试200问》系列博客&#xff01; &#x1f680;大家好&#xff0c;我是Jinkxs&#xff0c;一名热爱Java、深耕技术一线的开发者。在准备和参与了数十场Java面试后&#xff0c;我深知面试不仅是对知识的考察&#xff0c;更是对理解深度与表达能力的…

《VMware 安装 CentOS 7.9 虚拟机详细教程(含图解步骤)》

目录1.安装前准备1.1 准备VMware软件1.1.1 方式一1.1.2 方式二1.2 准备centos7.9镜像1.2.1 方式一1.2.2 方式二2.安装centos7.91.安装前准备 1.1 准备VMware软件 VMware需要的激活码百度直接搜索vmware workstation17激活码就可以搜索到 1.1.1 方式一 这种方式需要注册官网的…

新能源知识库(84)什么是IEC白皮书

IEC白皮书是由国际电工委员会&#xff08;IEC&#xff09;发布的战略性技术文件&#xff0c;旨在针对新兴技术和社会发展趋势&#xff0c;提出标准化需求和发展路径&#xff0c;为全球产业提供前瞻性指导。在新能源领域&#xff0c;IEC白皮书是推动技术创新、产业协同和国际规则…

从零开始学习JavaWeb-15

​​一、数据库安全与防注入实战​​1. ​​SQL 注入原理与危害​​​​攻击本质​​&#xff1a;利用输入漏洞篡改 SQL 语义&#xff0c;例如&#xff1a;SELECT * FROM users WHERE username admin OR 11 -- AND password xxxOR 11导致条件永真&#xff0c;绕过密码验证。​…

深入理解深度学习中的“Batch”

文章目录 **一、什么是Batch?为什么需要它?** **二、Batch Size(批次大小)的影响** **三、Batch, Epoch 和 Iteration 的关系** **四、案例分析** 在深度学习领域,“Batch”(批次)是一个核心且至关重要的概念。它指的是在模型训练过程中,一次性输入给神经网络进行处理的…

27.语言模型

语言模型&#xff0c;是NLP方向一直主力研究的&#xff0c;通过训练机器&#xff0c;来让机器学习人类语言的内在规律&#xff0c;理解自然语言&#xff0c;并将其转换为计算机语言。 目前的主流语言模型&#xff0c;如GPT、Deepseek等&#xff0c;并不是简单的搜索背诵。他们的…

小智ai+mcp+n8n的智能组合

小智aimcpn8n的智能组合1 小智ai的版本2 n8n的配置3 mcp的demo4 工作流json​ 之前有写过小智ai的介绍&#xff0c;它提供了流畅且丰富的用户语音交互能力。n8n提供了灵活且稳定的后台工作流的能力&#xff0c;如果这两个工具进行组合&#xff0c;可以打造一个好玩又好用的智能…

【DataGrip】连接达梦数据库后,能查询数据但是看不到表的几种情况分析,达梦数据库驱动包下载DmJdbcDriver18.jar

大概分为以下两类情况&#xff0c;配置问题和驱动包的问题 DmJdbcDriver18.jar点击下载 1.配置了表不可见 左上角点击过滤的图标&#xff0c;把table勾上就可以 2.Introspect using JDBC metadata 未勾选 1&#xff09;老版本的DataGrip 在options选项下 3&#xff09;新版…

全面解析 `strncasecmp` 字符串比较函数

1) 函数的概念与用途 strncasecmp 是 C 语言中一个非常实用的字符串处理函数&#xff0c;它执行不区分大小写的字符串比较&#xff0c;但只比较前 n 个字符。这个函数的名字来源于"string n case-compare"&#xff08;字符串前n个字符不区分大小写比较&#xff09;。…

高级SQL优化 | 告别 Hive 中 GROUP BY 的大 KEY 数据倾斜!PawSQL 自适应优化算法详解

数据倾斜让你的Hive查询慢如蜗牛&#xff1f;单个热点分组拖垮整个集群&#xff1f;PawSQL独家算法GroupSkewedOptimization来拯救&#xff01;&#x1f3af; 痛点直击&#xff1a;当数据倾斜遇上分组操作想象这样一个场景&#xff1a;你的电商平台有1000万VIP用户订单和100万普…

HUMS 2023齿轮箱数据分析

HUMS问答&#xff1a;https://humsconference.com.au/HUMS2023datachallenge/questions-answers.html 数据集申请&#xff1a;https://www.dst.defence.gov.au/our-technologies/helicopter-main-rotor-gearbox-planet-gear-fatigue-crack-propagation-test 历年试卷&#xff1…

智慧工地:科技赋能与管理革新下的建筑业新图景

随着数字技术的深度渗透&#xff0c;智慧工地正以“技术落地 行业变革 管理创新”的三重突破&#xff0c;重构施工场景的核心逻辑&#xff0c;推动建筑业从传统粗放式发展向精细化、智能化转型。一、技术落地&#xff1a;用科技筑牢安全防线&#xff0c;提升施工效率技术是智…