推荐用 LinkedHashMap 实现 LRU 缓存,因其底层基于双向链表+哈希表,设置 accessOrder=true 可自动维护访问顺序,重写 removeEldestEntry 即可淘汰最久未用项;手写方式需 Node、双向链表和 HashMap 配合,注意指针更新与 map 同步。
在 java 中用双向链表实现 lru 缓存,核心是用 linkedhashmap(底层即双向链表 + 哈希表)或手写 node + doublylinkedlist + hashmap 组合。前者简洁高效,后者更利于理解 lru 机制和面试考察。
用 LinkedHashMap 快速实现(推荐日常使用)
Java 的 LinkedHashMap 天然支持访问顺序(accessOrder = true),每次 get 或 put 都会把对应节点移到链表尾部;当容量超限时,可重写 removeEldestEntry 方法自动淘汰头节点(最久未用)。
示例代码:
class LRUCache<k v> extends LinkedHashMap<k v> {
private final int capacity;
public LRUCache(int capacity) {
// true 表示按访问顺序排序(get/put 都算访问)
super(capacity, 0.75f, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<k v> eldest) {
return size() > capacity; // 超过容量就移除最老的
}
}</k></k></k>
✅ 优点:代码少、线程不安全但性能高;
❌ 注意:默认 put 不触发访问顺序更新(除非 key 已存在),若需“写入也算最近使用”,确保 key 存在时用 replace 或先 get 再 put,或改用手写方式。
手写双向链表 + HashMap(适合学习与面试)
关键组件:
- Node 类:含 key、value、prev、next 字段
- 双向链表:维护 head(伪头,指向最新)、tail(伪尾,指向最久)
-
HashMap
:O(1) 定位节点
操作逻辑:
- get(key):查 map → 找到 node → 从链表中移出 → 插入 tail 前(即移到末尾)→ 返回 value
- put(key, value):若 key 存在 → 更新值 + 移至末尾;若不存在 → 新建 node 插入末尾 + 放入 map;若超容 → 删除 head.next(最久节点)并从 map 移除其 key
小技巧:用伪头(dummy head)和伪尾(dummy tail)避免空指针判断,插入删除统一。
关键细节与易错点
- 双向链表的 插入和删除必须同时更新前后节点的指针,漏掉任一端会导致链断裂或循环引用
- HashMap 中存储的是 Node 引用,不是值拷贝;node 的 key 和 value 需与 map 的 key/value 类型一致
- put 时若 key 已存在,不能只更新 value,还要把该 node 从原位置移出再插到尾部,否则访问顺序失效
- 删除最久节点时,记得同步从 HashMap 中
remove(key),否则内存泄漏且下次 get 会误判
是否需要线程安全?
如果多线程读写,基础版本会出错。可选方案:
- 用
Collections.synchronizedMap(new LRUCache(...))—— 粗粒度锁,影响并发性能 - 对关键方法(get/put)加
synchronized—— 简单直接 - 用
ReentrantLock分离读写锁(如读多写少场景)—— 更精细但复杂 - 生产环境建议直接用
caffeine或guava Cache,它们已做高度优化
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











