LinkedHashMap 实现 LRU 缓存的核心在于 head、tail 和 accessOrder 三者协同:head 指向最老节点,tail 指向最新节点,accessOrder=true 时每次 get/put 触发 afterNodeAccess 将节点移至 tail;removeEldestEntry 仅决策是否淘汰 head,链表维护由 before/after 指针保障 O(1) 操作。

LinkedHashMap 实现 LRU 缓存的核心,不在“有多 fancy”,而在它如何用两个简单变量 + 一个布尔开关,把访问顺序稳稳管住。关键不是写多少代码,而是看懂 head、tail 和 accessOrder 这三个成员变量怎么协同工作。
head 和 tail 是真实存在的双向链表锚点
LinkedHashMap 内部定义了两个 transient 成员:
- head:指向双向链表中“最老”的节点(即最近最少被访问的那个)
- tail:指向双向链表中“最新”的节点(即刚插入或刚被 get 的那个)
它们不是逻辑标记,而是实实在在参与链表操作的引用。每次 put 或 get 触发访问顺序更新时,LinkedHashMap 都会调用 afterNodeAccess 方法,把对应 Entry 节点从原位置摘下,再插到 tail 后面——也就是链表末尾。这个过程不依赖索引,只靠 before/after 指针重连,所以是 O(1)。
accessOrder 决定链表“按什么动”
这个 final boolean accessOrder 是开关型变量,初始化后不可变:
- false(默认):链表只反映插入顺序;put 时新节点加在 tail 后,get 不改变位置
-
true:链表反映访问顺序;每次 get 或 put(含更新)都会触发
afterNodeAccess,把对应节点移到 tail
注意:put 本身既是插入也是访问,只要 accessOrder=true,它就会让节点“上浮”到最新位置。这也是为什么 LRU 缓存必须传 true 构造 —— 否则 get 不会刷新热度。
removeEldestEntry 是淘汰决策出口,但不负责移动
这个 protected 方法本身不操作链表,只做判断:
- 它在每次 put 完成后被回调,传入当前 head 所指的“最老 Entry”
- 你只需返回 true 或 false,告诉 LinkedHashMap:“要不要删掉这个 head?”
- 真正删除动作由父类自动完成:先从哈希表中 remove(key),再从双向链表中 unlink head,并更新 head 引用
也就是说,head 始终是“待淘汰候选”,而 removeEldestEntry 就是那个拍板的人。它不碰 tail,也不改 before/after,纯粹是策略层接口。
Entry 节点自带前后指针,构成链表骨架
LinkedHashMap.Entry 继承自 HashMap.Node,额外增加了两个字段:
- before:指向前一个 Entry
- after:指向后一个 Entry
正是这两个引用,让所有 Entry 在插入时就能串成一条双向链。HashMap 负责 O(1) 查找,这套指针负责 O(1) 移动和裁剪——两者缺一不可。没有 before/after,就只是个普通哈希表;没有哈希表,光有链表查 key 就得遍历,LRU 就废了一半。










