集合(四):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 钩子方法,极大地简化了缓存淘汰策略的实现。
相关推荐
期待のcode1 小时前
Java虚拟机的垃圾对象判定
java·开发语言·jvm
我命由我123451 小时前
Android 开发 - 关于 startActivity 后立刻 finish、requestWindowFeature 方法注意事项
android·java·开发语言·java-ee·kotlin·android studio·android-studio
ZeroToOneDev1 小时前
SpringMvc
java·spring
坚持学习前端日记1 小时前
认证模块文档
java·服务器·前端·数据库·spring
Whoami!1 小时前
❿⁄₁₁ ⟦ OSCP ⬖ 研记 ⟧ 密码攻击实践 ➱ NTLM哈希传递攻击
网络安全·信息安全·哈希算法·密码破解·ntlm哈希传递
qq_12498707531 小时前
基于springboot的文化旅游小程序(源码+论文+部署+安装)
java·spring boot·后端·微信小程序·小程序·毕业设计·旅游
num_killer9 小时前
小白的Langchain学习
java·python·学习·langchain
期待のcode10 小时前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐10 小时前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat