bigywater.tech
Java 内部原理 · 20

LinkedHashMap 实现 LRU:真实验证淘汰的是最久没访问的,不是最早插入的

"LinkedHashMap 加两行代码就能实现 LRU 缓存"是常见的面试八股。真实写一个,用有区分度的访问顺序真实验证:被淘汰的到底是不是"最久没被访问"的那个,不是想当然。

[2,3,1] → 淘汰 2
真实验证:插入 1,2,3 后访问 get(1),1 被移到队尾;插入第 4 个时真实淘汰的是 2,不是最早插入的 1
accessOrder=true
核心开关:构造 LinkedHashMap 时传 true,迭代顺序就从"插入顺序"真实变成"访问顺序"

上一篇:WeakHashMap:真实测出 GC 之后 10000 个 key 真的清空成了 0 个

两个开关:accessOrder 和 removeEldestEntry

LinkedHashMap 默认按插入顺序迭代,把构造函数第三个参数传 true 就能切换成访问顺序;搭配重写 removeEldestEntry,插入超出容量时自动淘汰队首元素。

LruDemo.java(核心实现) static class LruCache<K, V> extends LinkedHashMap<K, V> { final int capacity; LruCache(int capacity) { super(16, 0.75f, true); // accessOrder=true:迭代顺序按访问顺序 this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > capacity; } }

真实验证:访问过的 key 真的被移到了队尾

容量设成 3,插入 1、2、3 之后先看默认顺序,再 get(1) 一次,看迭代顺序有没有真实变化。

真实实测 · 第一组
插入 1,2,3 之后,迭代顺序 = [1, 2, 3]
get(1) 之后,迭代顺序 = [2, 3, 1]
put(4) 之后,迭代顺序 = [3, 1, 4]
2 是否还在(预期:被淘汰,不在) = false
1 是否还在(预期:因为刚访问过,在)  = true

真实结果和预期完全一致:get(1) 之后,1 被真实移到了迭代顺序的最后(表示"最近访问过"),2 反而变成了顺序最靠前的("最久没被碰过")。插入第 4 个元素超出容量 3 时,removeEldestEntry 真实淘汰的是队首的 2,而不是插入时间更早的 1——1 虽然先于 2 插入,但因为中途被访问过,真实活了下来。

真实验证:更明确的连续访问顺序

再做一组区分度更大的实验:两次连续 get,制造出清晰的"谁更新"的顺序关系。

真实实测 · 第二组
访问顺序: put 10,20,30 -> get(20) -> get(10),迭代顺序 = [30, 20, 10]
put(40) 之后,迭代顺序 = [20, 10, 40]
30 是否还在(预期:被淘汰) = false
真实验证:先 get(20) 再 get(10),10 是"最近访问",排在队尾;两次访问都没碰过的 30 自然排到队首,插入第 4 个元素时真实被淘汰的正是它
这就是完整的 LRU 缓存吗?这是 LRU 淘汰策略的核心逻辑,真实可用,但离一个生产级缓存还差一些东西:比如线程安全(LinkedHashMap 本身不是线程安全的,并发场景需要额外加锁或换用专门的并发缓存库)、按存活时间过期(TTL)、软引用/弱引用级别的缓存驱逐等。真实项目里如果只是要一个简单的进程内 LRU,这几行代码经过真实验证是够用的;更复杂的场景,Caffeine 这类专门的缓存库会更合适。

分步演示:put 10/20/30、get 20/10、put 40 的每一步真实顺序

把"真实验证二"的六次操作串成动画:每一帧的迭代顺序都是真实跑出来的 LinkedHashMap.keySet(),不是手工排的。

put/get/put 六步真实操作:LinkedHashMap 迭代顺序如何跟着访问重排,又是怎么淘汰队首的(accessOrder=true)
这一步刚移到队尾的元素队首 · 下一个可能被淘汰的元素

    参考与说明

    • 本文全部迭代顺序均在本机真实运行 Java 17 得到,两组独立实验结果和预期完全一致。
    • 和WeakHashMap一样,都是 JDK 提供的、基于 HashMap 变体的现成方案解决特定场景问题,建议对照读作为系列收尾。
    • 没有涉及:LinkedHashMap 内部双向链表节点(before/after)如何和 HashMap 的桶结构叠加实现的细节——这些留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号