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

一文搞懂 LinkedHashMap 底层原理

LinkedHashMap 是 HashMap 的“增强版”,核心是在 HashMap 基础上增加了双向链表,既保留了 HashMap 的高效查找特性,又能维护键值对的插入顺序/访问顺序。本文从核心结构、底层原理、核心方法到使用场景,帮你彻底搞懂它。

一、先明确核心定位

LinkedHashMap 继承自 HashMap,实现了 Map 接口,它的核心特点:

  1. 底层基础:复用 HashMap 的“数组+链表+红黑树”结构,哈希表部分完全继承 HashMap 的逻辑;
  2. 核心增强:额外维护一条双向链表,记录所有键值对的访问顺序;
  3. 有序性:支持两种顺序(默认插入顺序):
    • 插入顺序:按键值对 put 的先后顺序排列;
    • 访问顺序:按键值对被 get/put 访问的先后顺序排列(最近访问的在链表尾部)。

二、核心结构:HashMap + 双向链表

1. 底层数据结构拆解

LinkedHashMap 的底层是“哈希表 + 双向链表”的组合,我们先看关键的内部类和核心字段:

(1)核心内部类:Entry(节点)

LinkedHashMap 重写了 HashMap 的 Node 节点,增加了 beforeafter 指针,用于构建双向链表:

// LinkedHashMap 的节点类,继承自 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);}
}

对比 HashMap 的 Node 节点(只有 next 指针,单向链表),LinkedHashMap 的 Entry 多了 before/after,既能参与哈希表的链表/红黑树,又能参与双向链表。

(2)核心字段

LinkedHashMap 新增了 3 个核心字段,用于维护双向链表:

public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {// 双向链表的头节点(最老的节点)transient LinkedHashMap.Entry<K,V> head;// 双向链表的尾节点(最新的节点)transient LinkedHashMap.Entry<K,V> tail;// 顺序模式:true=访问顺序,false=插入顺序(默认)final boolean accessOrder;
}

2. 结构可视化

用一张图理解 LinkedHashMap 的整体结构:

哈希表(数组+链表):
索引0: null
索引1: Entry1(hash, k1, v1, next=null) —— 同时属于双向链表
索引2: Entry2(hash, k2, v2, next=Entry3)↓Entry3(hash, k3, v3, next=null)
...双向链表:
head → Entry1 → Entry2 → Entry3 → tail↑    ↓    ↑    ↓    ↑    ↓before after before after before after
  • 哈希表负责快速查找(O(1) 时间复杂度);
  • 双向链表负责维护顺序,遍历的时候直接走双向链表,保证顺序性。

三、核心原理:关键方法的实现逻辑

LinkedHashMap 几乎没有重写 HashMap 的 put/get 核心方法,而是通过重写 HashMap 的钩子方法(回调方法)来维护双向链表,这是它的精髓。

1. HashMap 的钩子方法(LinkedHashMap 的核心重写点)

HashMap 中定义了 3 个空的钩子方法,供子类扩展:

// HashMap 中的钩子方法,供 LinkedHashMap 重写
// 节点插入后调用
void afterNodeInsertion(boolean evict) {}
// 节点访问后调用(get/put 已存在的节点)
void afterNodeAccess(Node<K,V> p) {}
// 节点删除后调用
void afterNodeRemoval(Node<K,V> p) {}

LinkedHashMap 正是通过重写这 3 个方法,实现双向链表的维护。

2. 插入元素(put):维护插入顺序

当调用 put(K key, V value) 时,流程如下:

  1. 调用 HashMap 的 put 方法,完成哈希表的插入(数组/链表/红黑树的插入);
  2. 触发 HashMap 的 afterNodeInsertion 钩子方法,LinkedHashMap 重写该方法,将新节点添加到双向链表的尾部
  3. 如果是访问顺序模式(accessOrder=true),还会触发 afterNodeAccess,把当前节点移到双向链表尾部。

核心代码(LinkedHashMap 重写的 newNodeafterNodeInsertion):

// 重写创建节点的方法,创建 LinkedHashMap.Entry(带 before/after)
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;
}// 把节点链接到双向链表尾部
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {LinkedHashMap.Entry<K,V> last = tail;tail = p;if (last == null) // 链表为空,头节点指向当前节点head = p;else { // 链表非空,当前节点的before指向原尾节点,原尾节点的after指向当前节点p.before = last;last.after = p;}
}

3. 访问元素(get):维护访问顺序

当调用 get(K key) 时,流程如下:

  1. 调用 HashMap 的 get 方法,找到对应的节点(哈希表查找);
  2. 如果是访问顺序模式(accessOrder=true),触发 afterNodeAccess 钩子方法,将该节点移到双向链表的尾部(表示最近访问);
  3. 返回节点的 value。

核心代码(LinkedHashMap 重写的 getafterNodeAccess):

public V get(Object key) {Node<K,V> e;if ((e = getNode(hash(key), key)) == null)return null;// 如果是访问顺序模式,将当前节点移到链表尾部if (accessOrder)afterNodeAccess(e);return e.value;
}// 访问节点后,将节点移到双向链表尾部
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;elseb.after = a;if (a != null)a.before = b;elselast = b;if (last == null)head = p;else {p.before = last;last.after = p;}tail = p;++modCount;}
}

4. 删除元素(remove):维护双向链表

当调用 remove(K key) 时:

  1. 调用 HashMap 的 remove 方法,从哈希表中删除节点;
  2. 触发 afterNodeRemoval 钩子方法,LinkedHashMap 重写该方法,将节点从双向链表中移除。

核心代码:

void afterNodeRemoval(Node<K,V> e) { // 移除节点时,调整双向链表LinkedHashMap.Entry<K,V> p =(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;// 清空当前节点的前驱/后继p.before = p.after = null;// 调整前后节点的指向if (b == null)head = a;elseb.after = a;if (a == null)tail = b;elsea.before = b;
}

5. LRU 缓存的核心:removeEldestEntry 方法

LinkedHashMap 提供了 removeEldestEntry 方法,默认返回 false,子类可以重写该方法实现LRU(最近最少使用)缓存:当链表长度超过阈值时,自动删除最老的节点(双向链表的头节点)。

示例代码(实现 LRU 缓存):

// 自定义 LinkedHashMap 实现 LRU 缓存,最多存储3个元素
class LRUCache<K, V> extends LinkedHashMap<K, V> {private final int MAX_CAPACITY;public LRUCache(int maxCapacity) {// 初始化:初始容量16,负载因子0.75,访问顺序模式super(16, 0.75f, true);this.MAX_CAPACITY = maxCapacity;}// 重写该方法:当元素数量超过阈值时,删除最老的节点(头节点)@Overrideprotected boolean removeEldestEntry(Map.Entry<K, V> eldest) {return size() > MAX_CAPACITY;}
}// 测试 LRU 缓存
public class LRUCacheTest {public static void main(String[] args) {LRUCache<Integer, String> cache = new LRUCache<>(3);cache.put(1, "A");cache.put(2, "B");cache.put(3, "C");System.out.println(cache); // {1=A, 2=B, 3=C}cache.get(1); // 访问1,移到尾部System.out.println(cache); // {2=B, 3=C, 1=A}cache.put(4, "D"); // 超过容量,删除最老的2System.out.println(cache); // {3=C, 1=A, 4=D}}
}

四、核心特性与使用场景

1. 核心特性

特性 说明
有序性 支持插入顺序/访问顺序,遍历顺序与维护的顺序一致
查找性能 与 HashMap 一致,O(1)(哈希表查找)
遍历性能 比 HashMap 快(直接遍历双向链表,无需遍历哈希表的空桶)
线程安全性 非线程安全(和 HashMap 一样),多线程场景需用 ConcurrentHashMap 或加锁
允许 null 键/值 与 HashMap 一致,允许一个 null 键、多个 null 值

2. 典型使用场景

  1. 需要保留插入顺序的键值对场景
    • 记录用户操作日志(按操作顺序存储);
    • 配置项的有序存储(按配置加载顺序读取)。
  2. LRU 缓存实现
    • 本地缓存(如用户会话缓存、热点数据缓存);
    • 有限容量的缓存,自动淘汰最近最少使用的元素。
  3. 有序遍历的场景
    • 需要按插入/访问顺序遍历键值对(如排行榜、最近访问列表)。

五、与 HashMap 的核心区别

对比维度 HashMap LinkedHashMap
底层结构 数组+链表+红黑树 数组+链表+红黑树+双向链表
顺序性 无序(按哈希值排列) 有序(插入/访问顺序)
遍历性能 较慢(需遍历空桶) 较快(直接遍历双向链表)
内存占用 较小 较大(多维护双向链表)
核心用途 快速查找、存储 有序存储、LRU缓存

总结

  1. 核心结构:LinkedHashMap = HashMap(哈希表) + 双向链表(维护顺序),通过重写 HashMap 的钩子方法实现链表维护;
  2. 顺序控制:默认按插入顺序,设置 accessOrder=true 按访问顺序(最近访问的在链表尾部);
  3. 核心价值:既保留 HashMap 的高效查找,又解决了 HashMap 无序的问题,是实现 LRU 缓存的最佳选择(无需自己维护顺序)。

记住核心逻辑:哈希表负责快查,双向链表负责有序,钩子方法负责链表维护

http://www.jsqmd.com/news/442819/

相关文章:

  • 企业福利新选择!畅回收批量回收大润发卡方案解析 - 畅回收小程序
  • 2026年智能手机面板出货量预计下降7.3%
  • stripe相关支付流程 流程示意图 - ace-
  • 深圳直达香港租车优质服务商推荐指南:福田直达香港包车、罗湖包车去香港机场、跨境包车业务、香港包车回广州选择指南 - 优质品牌商家
  • 拒绝黑盒飞行:40个核心术语拆解LLM从算力层到Agent层的工程架构
  • 威世(Vishay)推出 0404 封装 RGB LED,支持独立色彩控制
  • 2026北京道闸厂家排行榜 智慧停车设备精选 - 真知灼见33
  • 宝塔面板安装PHP提示 No package libwebp found解决方案
  • 2026九大CRM系统排行榜,全链路业务管理选型对比指南 - 毛毛鱼的夏天
  • 回收杉德斯玛特卡的秘密:解锁回收技巧,让优惠触手可及! - 团团收购物卡回收
  • 2026CRM系统排行榜,9款进销存一体化选型对比指南 - 毛毛鱼的夏天
  • 易基因|Plant Commun/IF11.6:内蒙古大学李东明团队利用植物WGBS揭示关键蛋白协同激活启动子甲基化基因转录
  • 全国马桶疏通优质品牌实用推荐榜:上门管道疏通、上门通下水、上门马桶疏通、马桶疏通、上门下水道疏通、上门地漏疏通选择指南 - 优质品牌商家
  • 2026年比较好的威海房产律师事务所品牌推荐:威海离婚律师事务所可靠体验推荐 - 行业平台推荐
  • 全球 AI+ 机器人高科技竞争未来趋势推演与深度研究报告
  • 2026年比较好的柴油发电机品牌推荐:静音柴油发电机/低噪音柴油发电机厂家热卖产品推荐(近期) - 行业平台推荐
  • 河南迪马新能源:太阳能路灯厂家标杆,3万套年产能赋能全国绿色照明 - 朴素的承诺
  • 深兰科技北欧订单落地、非洲医疗合作推进,全球业务结构加速延展
  • Ubuntu24.04开通SSH远程服务
  • 电磁脉冲阀供货市场新趋势,2026年优质厂家推荐,除尘器布袋/布袋除尘器/通风阀门,电磁脉冲阀订做厂家口碑推荐榜 - 品牌推荐师
  • 宝塔面板如何安装Redis扩展?
  • ssm+java的电子政务服务申请系统的研究与实现
  • 整形机供应商哪家强?2026年市场热门选择,热压整形机/伺服热压机/电子压床/粉末压机/伺服电子压力机,整形机厂家排行 - 品牌推荐师
  • 2026年评价高的储能品牌推荐:工商业储能/石油矿山储能/西安混合储能值得买的厂家 - 行业平台推荐
  • 河南迪马新能源:太阳能路灯生产厂家标杆,照亮全国绿色出行路 - 朴素的承诺
  • 2026CRM系统选型指南:7大品牌从客户管理到协同运营全维度对比 - 毛毛鱼的夏天
  • 2026年口碑好的空气过滤器品牌推荐:高效过滤器/耐高温高效过滤器厂家真实测评 - 行业平台推荐
  • 2026年岩石真三轴试验机定制生产厂家排名,东华卓越上榜 - 工业品网
  • 是德科技MSOX4154A DSOX4024A DSOX4034A DSOX4054A示波器
  • 2026数据中心高可靠性软连接供应商推荐指南 - 优质品牌商家