继承 LinkedHashMap 是最简可行方案,因其内置 accessOrder 和插入顺序维护,天然支持 LRU;只需重写 removeEldestEntry 判断 size() > maxSize 即可自动淘汰,无需手写链表或处理同步。

为什么继承 LinkedHashMap 是最简可行方案
因为 LinkedHashMap 内置访问顺序(accessOrder = true)和插入顺序维护,天然支持“最近最少使用”的排序基础。不用自己手写双向链表+哈希表,也避免了 ConcurrentHashMap 与手动同步的复杂性。
关键点在于:它把“淘汰逻辑”和“数据结构”解耦了——你只管决定“要不要删”,删除动作由父类自动在 put 后触发。
常见错误是忽略构造时传 true:不设 accessOrder,removeEldestEntry 就永远按插入顺序判断,不是 LRU。
removeEldestEntry 的触发时机和返回逻辑
这个方法在每次 put 或 putAll 成功后被调用,传入的是**即将被加入前**的当前 entrySet 视图(即还没加新 entry)。所以判断长度要用 size() >= maxSize,而不是 > maxSize。
立即学习“Java免费学习笔记(深入)”;
- 返回
true:父类立即移除最老 entry(按 accessOrder 就是最久未访问的那个) - 返回
false:不删,继续流程 - 不能在里面调用
remove或修改 map,会引发ConcurrentModificationException
示例:
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > maxSize;
}注意这里是 >,不是 >= —— 因为 put 后 size 已经 +1,此时若等于 maxSize 是合法的。
线程安全不是默认选项,别误以为“继承就线程安全”
LinkedHashMap 本身非线程安全,子类也不会自动变安全。如果多个线程并发 put,可能触发 removeEldestEntry 的竞态,甚至导致 size 计算错乱、漏删或多删。
两种务实选择:
- 外层用
Collections.synchronizedMap(new LRUCache(...)),但会锁整个 map,吞吐低 - 改用
ConcurrentHashMap+ 手写 LRU(如带时间戳的ConcurrentSkipListMap),但复杂度陡增 - 更推荐:用现成的
caffeine或guava Cache,它们内部用分段锁+异步清理,兼顾性能与语义正确
别为了“看起来简洁”而在线上服务里裸用非线程安全 LRU。
泛型擦除下,removeEldestEntry 的参数类型容易看走眼
方法签名是 removeEldestEntry(Map.Entry<K,V> eldest),但实际传入的 eldest 是当前 map 中**最老的那个 entry**,不是新 entry。有人误以为它是“将要插入的那个”,结果逻辑写反。
典型误用:
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return eldest.getValue() == null; // 错!这不是淘汰依据
}LRU 只看访问序和容量,跟 value 内容无关。
真正该关注的只有两个事实:当前 size() 和你设定的 maxSize;以及这个 eldest 确实是“最久没被 get/put 触达过的那个”。
调试时可临时加日志:System.out.println("eldest key=" + eldest.getKey() + ", size=" + size());,确认它是否真按访问顺序滚动。







