java基础-LinkedHashMap

在Java中,LinkedHashMap是HashMap的一个子类,它维护了一个双向链表来记录插入顺序或访问顺序。

LinkedHashMap的底层构成

LinkedHashMap是在HashMap的基础上,增加了双向链表来维护顺序。

1.核心数据结构

// LinkedHashMap内部类Entry继承了HashMap.Node static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; // 双向链表指针 Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }

2.实际存储结构

HashMap部分: 数组 + 链表/红黑树(解决哈希冲突) LinkedHashMap额外部分: 头节点(head) ↔ 节点1 ↔ 节点2 ↔ ... ↔ 尾节点(tail)(双向链表)

主要特点

1.有序性

  • 插入顺序:默认按元素插入的顺序维护

  • 访问顺序:可通过构造函数设置为按访问顺序维护

2.继承关系

Object ↳ AbstractMap<K,V> ↳ HashMap<K,V> ↳ LinkedHashMap<K,V>

基本用法

import java.util.LinkedHashMap; import java.util.Map; public class LinkedHashMapExample { public static void main(String[] args) { // 1. 创建LinkedHashMap(默认按插入顺序) LinkedHashMap<String, Integer> linkedMap = new LinkedHashMap<>(); // 添加元素 linkedMap.put("Apple", 100); linkedMap.put("Banana", 50); linkedMap.put("Orange", 80); linkedMap.put("Grape", 120); // 遍历 - 按插入顺序输出 System.out.println("按插入顺序:"); for (Map.Entry<String, Integer> entry : linkedMap.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } // 输出: Apple, Banana, Orange, Grape(按插入顺序) // 2. 按访问顺序(LRU顺序) LinkedHashMap<String, Integer> lruMap = new LinkedHashMap<>( 16, // 初始容量 0.75f, // 加载因子 true // accessOrder为true表示按访问顺序 ); lruMap.put("A", 1); lruMap.put("B", 2); lruMap.put("C", 3); System.out.println("\n访问前:"); lruMap.forEach((k, v) -> System.out.print(k + " ")); // A B C // 访问元素B lruMap.get("B"); System.out.println("\n访问B后:"); lruMap.forEach((k, v) -> System.out.print(k + " ")); // A C B } }

构造方法

// 1. 默认构造函数(插入顺序,初始容量16,加载因子0.75) LinkedHashMap<String, Integer> map1 = new LinkedHashMap<>(); // 2. 指定初始容量 LinkedHashMap<String, Integer> map2 = new LinkedHashMap<>(32); // 3. 指定初始容量和加载因子 LinkedHashMap<String, Integer> map3 = new LinkedHashMap<>(32, 0.8f); // 4. 指定初始容量、加载因子和排序模式 // true: 按访问顺序 false: 按插入顺序(默认) LinkedHashMap<String, Integer> map4 = new LinkedHashMap<>(32, 0.75f, true); // 5. 从其他Map复制 LinkedHashMap<String, Integer> map5 = new LinkedHashMap<>(existingMap);

与HashMap比较

特性HashMapLinkedHashMap
顺序无顺序插入顺序或访问顺序
性能O(1)略慢于HashMap(需要维护链表)
内存占用较少较多(需要额外存储链表指针)
迭代性能较慢(需要遍历整个table)较快(直接遍历链表)

哈希存储过程:

  1. 计算哈希:通过key.hashCode()计算

  2. 定位桶(n-1) & hash计算数组下标

  3. 处理冲突:链表或红黑树

  4. 维护顺序:同时添加到双向链表的尾部

详细工作原理图

HashMap结构(快速查找) LinkedHashMap额外结构(维护顺序) ↓ ↓ ┌───────┐ head → tail │ 数组 │ ↑ ↓ │[0] │ ┌─────┐ ┌─────┐ ┌─────┐ 插入"A" → 哈希 → │[1] → A│───────────────→│ A │←→│ │ │ │ │[2] │ └─────┘ └─────┘ └─────┘ │[3] │ └───────┘ ┌───────┐ head → tail │ 数组 │ ↑ ↓ │[0] │ ┌─────┐ ┌─────┐ ┌─────┐ 插入"B" → 哈希 → │[1] → A│───────────────→│ A │←→│ B │ │ │ │[2] → B│───────────────→│ │←→│ │ │ │ │[3] │ └─────┘ └─────┘ └─────┘ └───────┘ ┌───────┐ head → tail │ 数组 │ ↑ ↓ │[0] │ ┌─────┐ ┌─────┐ ┌─────┐ 插入"C" → 哈希 → │[1] → A│───────────────→│ A │←→│ B │←→│ C │ │[2] → B│───────────────→│ │←→│ │←→│ │ │[3] → C│───────────────→│ │←→│ │←→│ │ └───────┘ └─────┘ └─────┘ └─────┘

源码关键方法解析

// 1. 创建节点时,同时链接到链表尾部 Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<>(hash, key, value, e); linkNodeLast(p); // ← 关键!将新节点链接到链表尾部 return p; } // 2. 链接节点到链表尾部 private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } } // 3. 访问节点后调整顺序(LRU模式) void afterNodeAccess(Node<K,V> e) { // 移动节点到链表尾部 LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }

查找过程示例

public class LookupExample { public static void main(String[] args) { LinkedHashMap<String, Integer> map = new LinkedHashMap<>(); map.put("Apple", 100); map.put("Banana", 50); map.put("Orange", 80); // 查找"Banana"的过程: // 1. 计算hash: "Banana".hashCode() // 2. 定位桶: (n-1) & hash → 找到数组下标 // 3. 遍历链表/树: 在对应桶中查找 // 4. 如果accessOrder=true,调用afterNodeAccess()将节点移到链表尾部 Integer value = map.get("Banana"); // 通过哈希快速定位 System.out.println("Found: " + value); } }

性能特点对比

操作HashMapLinkedHashMap
查找O(1)O(1)(同样使用哈希)
插入O(1)O(1)(额外链表操作)
删除O(1)O(1)(额外链表操作)
迭代O(n)O(n)但更快(直接遍历链表)
内存较小每个节点多2个引用(before, after)

总结

  1. 底层构成:HashMap的数组+链表/红黑树 + 额外的双向链表

  2. key哈希完全使用HashMap的哈希机制,确保O(1)查找

  3. 顺序维护:通过双向链表维护插入顺序或访问顺序

  4. 设计巧妙:结合了HashMap的快速查找和链表的顺序特性

LinkedHashMap通过这种"哈希表+双向链表"的复合结构,既保证了HashMap的快速查找特性,又提供了可预测的迭代顺序,是一个非常精妙的设计!

进阶问题

LinkedHashMap作为一个Map与HashMap基本相同,为什么还需要添加一个排序功能,创造出LinkedHashMap?

确实,作为Map,核心功能是通过key找value。但维护顺序在很多实际场景中非常有用,让我通过具体例子来解释:

1.需要保持插入顺序的场景

示例:配置文件解析

public class ConfigFileParser { public static void main(String[] args) { // 读取配置文件,需要保持原有顺序 LinkedHashMap<String, String> config = new LinkedHashMap<>(); config.put("server.host", "localhost"); config.put("server.port", "8080"); config.put("database.url", "jdbc:mysql://localhost:3306/mydb"); config.put("database.username", "admin"); config.put("database.password", "secret"); // 保存配置时,保持原来的顺序 System.out.println("配置项(保持原顺序):"); config.forEach((key, value) -> System.out.println(key + " = " + value)); // 输出顺序与插入顺序一致,便于阅读和维护 } }

示例:操作日志记录

public class OperationLogger { public static void main(String[] args) { // 记录用户操作序列 LinkedHashMap<Long, String> operationLog = new LinkedHashMap<>(); operationLog.put(System.currentTimeMillis(), "用户登录"); Thread.sleep(100); operationLog.put(System.currentTimeMillis(), "查询商品"); Thread.sleep(100); operationLog.put(System.currentTimeMillis(), "添加购物车"); Thread.sleep(100); operationLog.put(System.currentTimeMillis(), "提交订单"); // 可以按操作发生的顺序回放 System.out.println("用户操作序列:"); operationLog.forEach((time, op) -> System.out.println(new Date(time) + " - " + op)); } }

2.需要可预测迭代顺序的场景

示例:生成有序的JSON/XML

public class OrderedJSONGenerator { public static void main(String[] args) { // 使用LinkedHashMap保证JSON字段顺序 LinkedHashMap<String, Object> json = new LinkedHashMap<>(); json.put("id", 1001); json.put("name", "张三"); json.put("age", 25); json.put("email", "zhangsan@example.com"); json.put("createdAt", "2024-01-15"); // 转换为JSON时,字段顺序是确定的 String jsonString = convertToJSON(json); System.out.println(jsonString); // 输出:{"id":1001,"name":"张三","age":25,"email":"zhangsan@example.com","createdAt":"2024-01-15"} // 如果不保证顺序,每次输出可能不同,不利于调试和测试 } static String convertToJSON(LinkedHashMap<String, Object> map) { StringBuilder sb = new StringBuilder("{"); for (Map.Entry<String, Object> entry : map.entrySet()) { sb.append("\"").append(entry.getKey()).append("\":"); sb.append("\"").append(entry.getValue()).append("\","); } sb.deleteCharAt(sb.length() - 1); sb.append("}"); return sb.toString(); } }

3.构建有序映射表

示例:字母频率统计(按字母顺序)

public class LetterFrequency { public static void main(String[] args) { String text = "hello world"; // 使用LinkedHashMap按字母顺序统计 LinkedHashMap<Character, Integer> frequency = new LinkedHashMap<>(); // 先初始化所有字母(保证顺序) for (char c = 'a'; c <= 'z'; c++) { frequency.put(c, 0); } // 统计频率 for (char c : text.toLowerCase().toCharArray()) { if (frequency.containsKey(c)) { frequency.put(c, frequency.get(c) + 1); } } // 按字母顺序输出 System.out.println("字母频率统计:"); frequency.forEach((letter, count) -> { if (count > 0) { System.out.println(letter + ": " + count); } }); // 输出:d:1, e:1, h:1, l:3, o:2, r:1, w:1 } }

核心价值:LinkedHashMap在不牺牲查找性能(O(1))的前提下,提供了可预测的迭代顺序,这在很多业务场景中非常有用。它结合了HashMap的快速查找和List的顺序特性,是一个"两全其美"的数据结构。

所以,虽然Map的核心是通过key找value,但顺序在很多实际应用中提供了额外的价值,而LinkedHashMap正好满足了这个需求!

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

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

相关文章

核心要点:确保准确测量USB3.0传输速度的关键步骤

如何真正测出USB3.0的真实速度&#xff1f;别再被“5Gbps”忽悠了你有没有遇到过这种情况&#xff1a;买了一块标称“读取450MB/s”的USB3.0移动硬盘&#xff0c;插上电脑一测&#xff0c;CrystalDiskMark显示写入才120MB/s&#xff1f;第一反应可能是“商家虚标”&#xff0c;…

嵌入式RS485驱动开发:完整指南与代码实现

嵌入式RS485驱动开发&#xff1a;从硬件到代码的实战指南在工业现场&#xff0c;你有没有遇到过这样的场景&#xff1f;一条长长的电缆穿过多台设备&#xff0c;连接着温湿度传感器、电表、PLC控制器——它们共享同一组信号线&#xff0c;却能互不干扰地通信。即使环境嘈杂、距…

Nat Commun新作:基于逆向设计的超紧凑铌酸锂多模光子集成系统

01前沿摘要近日&#xff0c;国际顶级期刊《Nature Communications》发表了一项光子集成领域的突破性研究(https://doi.org/10.1038/s41467-025-67927-7)。科学家们成功在薄膜铌酸锂平台上&#xff0c;利用“逆向设计”方法&#xff0c;实现了光子器件尺寸的数量级缩小与集成密度…

大学生就业招聘系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】

&#x1f4a1;实话实说&#xff1a;有自己的项目库存&#xff0c;不需要找别人拿货再加价&#xff0c;所以能给到超低价格。摘要 随着高校毕业生人数的逐年增加&#xff0c;就业市场竞争日益激烈&#xff0c;传统的线下招聘模式已无法满足高效、精准的求职需求。大学生就业信息…

【工具变量】分省城镇化率数据集(2005-2024年)

数据简介&#xff1a;城镇化率是指一个国家&#xff08;地区&#xff09;城镇的常住人口占该国家&#xff08;地区&#xff09;总人口的比例&#xff0c;是衡量城镇化水平高低&#xff0c;反映城镇化进程的一个重要指标。城镇化率是一个重要的经济和社会发展指标&#xff0c;能…

《Nat Commun》突破:我国团队研制全谱段集成电光调制器,为下一代超宽带光通信奠定芯片基础

01前言近日&#xff0c;国际顶级学术期刊《Nature Communications》发表了一项重磅成果(https://doi.org/10.1038/s41467-025-67902-2)。由华中科技大学、复旦大学、中国科学院半导体研究所等机构组成的联合团队&#xff0c;成功研制出一种基于“薄膜铌酸锂”的超宽带电光调制器…

基于SpringBoot+Vue的校园资料分享平台管理系统设计与实现【Java+MySQL+MyBatis完整源码】

&#x1f4a1;实话实说&#xff1a;有自己的项目库存&#xff0c;不需要找别人拿货再加价&#xff0c;所以能给到超低价格。摘要 随着信息技术的快速发展&#xff0c;校园资源共享的需求日益增长。传统的资料共享方式依赖线下传递或简单的文件存储&#xff0c;存在效率低、管理…

Kibana时间序列数据分析:elasticsearch客户端工具实战演示

用代码驾驭时间序列&#xff1a;Elasticsearch 客户端如何重塑 Kibana 数据分析体验你有没有遇到过这样的场景&#xff1f;Kibana 仪表板打开要等半分钟&#xff0c;图表加载到一半就超时&#xff1b;想查“上周同一天的接口延迟对比”&#xff0c;却发现图形界面根本没法做同比…

stm32毕业设计简单的题目怎么做

【单片机毕业设计项目分享系列】 &#x1f525; 这里是DD学长&#xff0c;单片机毕业设计及享100例系列的第一篇&#xff0c;目的是分享高质量的毕设作品给大家。 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的单片机项目缺少创新和亮点…

企业级在线教育系统管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】

&#x1f4a1;实话实说&#xff1a; 有自己的项目库存&#xff0c;不需要找别人拿货再加价&#xff0c;所以能给到超低价格。 摘要 随着信息技术的快速发展和互联网的普及&#xff0c;在线教育已成为现代教育体系中不可或缺的一部分。企业级在线教育系统通过数字化手段打破了传…

通俗解释es客户端工具如何管理索引

用好 es客户端工具&#xff0c;轻松玩转 Elasticsearch 索引管理 你有没有遇到过这样的场景&#xff1a;半夜收到告警&#xff0c;日志系统突然写不进数据了。一查才发现&#xff0c;原来是某个服务上线时忘了创建对应的索引模板&#xff0c;导致新日志被拒之门外。更头疼的是…

思科:速修复已出现 exp 的身份服务引擎漏洞

聚焦源代码安全&#xff0c;网罗国内外最新资讯&#xff01;编译&#xff1a;代码卫士思科修复了位于身份服务引擎 (ISE) 网络访问控制解决方案中的一个漏洞CVE-2026-20029。目前已出现该漏洞的公开利用代码&#xff0c;可被攻击者以管理员权限利用。企业管理员在执行零信任架构…

收藏!字节/阿里/腾讯大模型面试高频题拆解(含高分模板+无项目造亮点技巧)

最近后台收到几十条私信&#xff0c;全是程序员和入门小白关于大模型面试的吐槽&#xff0c;句句戳中痛点&#xff1a; “面字节被问‘Agent怎么设计记忆机制’&#xff0c;我只知道Agent能调用工具&#xff0c;当场卡壳说不出话”&#xff1b; “简历写了做过RAG项目&#xff…

房价跌30%,月供3.5万每天亏1k?这个AI岗位3年赚100w+,普通人也能冲?

刷到网友分享的一则扎心案例&#xff1a;朋友入手了单价9万的房子&#xff0c;如今房价直接跌了30%&#xff0c;每月还要背负3.5万的房贷&#xff0c;算下来每天一睁眼&#xff0c;就相当于亏了1000块……图片来源网络&#xff0c;侵删 评论区里满是唏嘘&#xff0c;不少网友留…

利用es查询语法进行错误日志定位:完整示例解析

用好 ES 查询语法&#xff0c;让错误日志无处遁形&#xff1a;实战全解析 你有没有过这样的经历&#xff1f;凌晨两点&#xff0c;告警突然炸响&#xff0c;接口成功率断崖式下跌。你手忙脚乱地登录服务器&#xff0c; tail -f 几个日志文件&#xff0c;眼睛在滚动的字符流里…

2026大模型交付指南:从聊天到办事,程序员必备收藏

2026年AI将进入"交付期"&#xff0c;从能聊走向能办事&#xff0c;从生成内容走向编排流程。Agentic AI将规模化&#xff0c;软件开发范式从写代码转向指挥交付&#xff0c;世界模型将赋予AI空间物理智能。端侧AI回流、网络安全攻防质变、行业应用深水区拓展&#xf…

VS:注释

在 Visual Studio 中取消注释的快捷键是 ‌CtrlK 后按 CtrlU‌&#xff08;需先选中代码&#xff09;。‌‌注释快捷 是 先按 CtrlK&#xff0c;再快速按 Ctrl/操作步骤&#xff1a;‌选中代码‌&#xff1a;用鼠标拖选或键盘&#xff08;Shift方向键&#xff09;选择要取消注释…

HID与USB协议关系:新手也能懂的图解说明

从键盘到游戏手柄&#xff1a;HID与USB是如何“对话”的&#xff1f;一文讲透人机交互的底层逻辑 你有没有想过&#xff0c;为什么你的机械键盘插上电脑就能立刻打字&#xff0c;而不需要安装任何驱动&#xff1f;为什么你在Mac上用过的鼠标&#xff0c;拿到Windows笔记本上也…

EasyGBS算法算力平台重构服务业视频监控AI应用

在数字化浪潮席卷全球的今天&#xff0c;服务业正经历着从传统模式向智能化、精细化管理的深刻变革。无论是连锁零售、酒店餐饮、健康养老&#xff0c;还是文化旅游如何在保障服务质量、提升运营效率的同时&#xff0c;确保客户安全与体验&#xff0c;成为行业共同面临的课题。…

【技术精选】智能体路由模式深度解析:让你的AI系统像人类一样“见机行事“(含代码示例)

路由模式是智能体系统的"动态决策中枢"&#xff0c;通过"接收输入→评估决策→导向路径"的闭环机制&#xff0c;使智能体摆脱固定流程&#xff0c;实现灵活应变。文章详细解析了路由模式的定义、价值、4种主流实现方式&#xff08;基于LLM、嵌入、规则、机…