集合(四):LinkedHashMap深度解析——有序哈希与 LRU 缓存的基石

文章目录

    • [1. 核心架构:哈希表与链表的完美融合](#1. 核心架构:哈希表与链表的完美融合)
      • [1.1 结构思维导图](#1.1 结构思维导图)
      • [1.2 节点结构的秘密](#1.2 节点结构的秘密)
      • [1.3 结构示意图](#1.3 结构示意图)
    • [2. 核心机制:accessOrder 与 顺序维护](#2. 核心机制:accessOrder 与 顺序维护)
      • [2.1 访问顺序移动流程(LRU 基础)](#2.1 访问顺序移动流程(LRU 基础))
      • [2.2 源码解析:afterNodeAccess](#2.2 源码解析:afterNodeAccess)
    • [3. 实战:3分钟实现 LRU 缓存](#3. 实战:3分钟实现 LRU 缓存)
      • [3.1 关键方法:removeEldestEntry](#3.1 关键方法:removeEldestEntry)
      • [3.2 LRU 缓存完整代码](#3.2 LRU 缓存完整代码)
      • [3.3 LRU 淘汰流程图](#3.3 LRU 淘汰流程图)

在 Java 集合框架中,HashMap 以其高效的查找性能称霸,但它有一个痛点:无序 。当你需要一个既能快速查找,又能保持插入顺序(或访问顺序)的容器时,LinkedHashMap 便是最佳选择。

本文将揭示 LinkedHashMap 如何通过"双重结构"实现有序性,并展示如何利用其独有的机制,用短短几行代码实现一个 LRU (Least Recently Used) 缓存。

1. 核心架构:哈希表与链表的完美融合

LinkedHashMap 继承自 HashMap,它并没有重写哈希算法,而是通过在原有哈希桶的基础上,维护了一条双向链表

1.1 结构思维导图

LinkedHashMap
继承关系
extends HashMap
implements Map
核心数据结构
哈希表 (数组 + 链表/红黑树): 保证 O(1) 查找
双向链表 (head + tail): 保证迭代顺序
关键属性
accessOrder
false (默认): 插入顺序
true: 访问顺序 (LRU 核心)
Entry 节点
继承 HashMap.Node
新增 before 指针
新增 after 指针
实战应用
LRU 缓存
有序配置加载

1.2 节点结构的秘密

普通的 HashMap 节点只有 next 指针(用于处理哈希冲突)。而 LinkedHashMap 的节点(Entry)增加了 before and after 指针,用于串联起所有元素。

java 复制代码
// LinkedHashMap 内部类 Entry
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);
    }
}

1.3 结构示意图

下图展示了 LinkedHashMap 的内存视图。注意看,数据既存在于哈希桶中(垂直方向),也被一条双向链表(水平方向的红色虚线)串联起来。
HashMap Structure
after
before
after
before
after
before
Bucket 0
Bucket 1
Bucket 2
Bucket 3
Key: A

Val: 1
Key: B

Val: 2
Key: C

Val: 3
Key: D

Val: 4


2. 核心机制:accessOrder 与 顺序维护

LinkedHashMap 的构造函数中有一个至关重要的参数:accessOrder

  • accessOrder = false (默认) : 插入顺序。新插入的元素放在链表末尾;读取元素不会改变链表顺序。
  • accessOrder = true : 访问顺序 。每当 getput 一个已存在的元素时,该元素会被移动到链表的末尾

2.1 访问顺序移动流程(LRU 基础)

假设我们开启了 accessOrder = true,当前链表为 A <-> B <-> C。当我们调用 get("A") 时:
DoubleLinkedList LinkedHashMap Client DoubleLinkedList LinkedHashMap Client 将 A 从当前位置断开 将 A 移动到链表尾部 get("A") 查找 Key "A" (HashMap逻辑) afterNodeAccess(Node A) 链表变为 B <->> C <->> A return Value A

2.2 源码解析:afterNodeAccess

HashMap 预留了三个回调方法给子类实现,LinkedHashMap 重写了其中的 afterNodeAccess

java 复制代码
// 当 accessOrder 为 true 时,将当前节点 e 移到双向链表尾部
void afterNodeAccess(Node<K,V> e) { 
    LinkedHashMap.Entry<K,V> last;
    // 条件:accessOrder 为 true 且 当前节点不是尾节点
    if (accessOrder && (last = tail) != e) {
        // ... (省略具体的指针断开与重连代码) ...
        // 核心逻辑:
        // 1. 将 e 从 before 和 after 之间移除
        // 2. 将 e 接到 tail 后面
        // 3. tail 指向 e
    }
}

3. 实战:3分钟实现 LRU 缓存

LRU (Least Recently Used) 是一种常见的缓存淘汰算法:当缓存满时,优先淘汰最近最少使用的数据。

利用 LinkedHashMapaccessOrder=true 特性,链表头部 就是"最近最少使用"的元素,尾部是"最近刚被使用"的元素。我们只需要在插入新元素导致容量溢出时,移除链表头部的元素即可。

3.1 关键方法:removeEldestEntry

LinkedHashMap 提供了一个受保护的方法 removeEldestEntry。在每次 put 新元素后,HashMap 会调用这个方法。

  • 默认实现返回 false(永不移除)。
  • 我们需要重写它:当 size() > capacity 时返回 true

3.2 LRU 缓存完整代码

java 复制代码
import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    
    private final int capacity;

    public LRUCache(int capacity) {
        // 1. 设置初始容量
        // 2. loadFactor 设为 0.75
        // 3. 关键点:accessOrder 设为 true (按访问顺序排序)
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    /**
     * 核心钩子方法
     * 每次 put 操作后,LinkedHashMap 会调用此方法
     * 如果返回 true,则移除链表最老的节点(头部节点)
     */
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > capacity;
    }

    // 测试代码
    public static void main(String[] args) {
        // 创建一个容量为 3 的 LRU 缓存
        LRUCache<String, Integer> cache = new LRUCache<>(3);

        cache.put("A", 1);
        cache.put("B", 2);
        cache.put("C", 3);
        System.out.println("初始状态: " + cache); // [A, B, C]

        cache.get("A"); // 访问 A,A 变成最新的,移到尾部
        System.out.println("访问 A 后: " + cache); // [B, C, A]

        cache.put("D", 4); // 插入 D,容量超标,淘汰最老的 B
        System.out.println("插入 D 后: " + cache); // [C, A, D]
    }
}

3.3 LRU 淘汰流程图



put 新元素 D
HashMap 插入 D
将 D 链接到链表尾部
调用 removeEldestEntry

size > capacity ?
移除链表头部节点

(最近最少使用的)
结束


LinkedHashMap 是 Java 集合框架中设计非常精妙的一个类,它展示了如何通过组合现有的数据结构(数组+链表)来满足复杂的业务需求。

  1. 双重结构 :继承 HashMap 保证查询效率,内部维护双向链表保证迭代顺序。
  2. accessOrder:决定了是按"插入顺序"还是"访问顺序"维护链表,这是实现 LRU 的开关。
  3. 扩展性 :通过预留的 removeEldestEntry 钩子方法,极大地简化了缓存淘汰策略的实现。
相关推荐
就叫_这个吧2 小时前
Java递归方法实现面包屑导航
java·开发语言
fīɡЙtīиɡ ℡2 小时前
AI 应用系统设计
java·开发语言·人工智能
小飞学编程...2 小时前
【哈希表】
数据结构·哈希算法·散列表
城管不管2 小时前
重生——第十一次面试之挖财一面2026.8.19已OC
java·服务器·jvm·数据库·spring·面试·职场和发展
码匠许师傅2 小时前
【C++ 面试真题】26. 聊聊 C++ 的智能指针
java·c++·面试
AI绘画哇哒哒2 小时前
【建议收藏!】35岁后端血泪忠告,这3类人别硬转Agent(过来人亲述)
java·人工智能·后端·ai·程序员·大模型·agent
最强小杰3 小时前
gpt-5.6-sol 频繁报 503 怎么办?区分容量熔断和限速 429 的排查方法 + 可复用 retry wrapper
java·人工智能·gpt·ai
吠品5 小时前
Wine 在 Linux 上运行 Windows 软件完整指南
java·linux·服务器
亚历克斯神6 小时前
智能搜索系统的升级复盘——从 Elasticsearch 到混合检索的检索质量提升
java·spring·微服务
金銀銅鐵7 小时前
[Java] 一个方法最多可以有多少个入参?
java·jvm