直接继承 linkedhashmap 可实现 lru,因其支持 accessorder=true 模式,get/put 移动节点至尾部,且 removeeldestentry() 在插入后被调用以判断是否淘汰头节点;正确实现只需返回 size() > capacity。

为什么直接继承 LinkedHashMap 就能实现 LRU?
因为 LinkedHashMap 内置访问顺序模式(accessOrder = true),每次 get() 或 put() 都会把对应节点移到链表尾部;而它的 removeEldestEntry() 方法会在每次插入后被调用,正好用来判断是否该淘汰头节点。
这不是“巧用”,而是 JDK 明确设计的支持机制——但很多人误以为要自己维护双向链表。
- 必须在构造时传
accessOrder = true,否则默认是插入顺序,get()不会改变顺序 -
removeEldestEntry()返回true才会删除最老项,返回false什么也不做 - 重写该方法时,不能依赖
size()判断容量超限——因为put()还没完成,此时size()是旧值;应判断size() > capacity(注意:这是安全的,JDK 文档明确保证此时size()已含新 entry)
removeEldestEntry() 里写错条件会导致缓存不淘汰
典型错误是写成 if (size() >= capacity) 或更糟:用 keySet().size()、甚至在方法里手动计数。这些都会导致缓存始终不驱逐,或驱逐时机错乱。
正确写法只有一行逻辑:return size() > capacity;
立即学习“Java免费学习笔记(深入)”;
- 容量检查必须用
size() > capacity,不是>=—— 因为插入新项后 size 已 +1,若允许等于,就永远卡在满状态 - 不要在
removeEldestEntry()里调用get()、put()或任何可能触发结构变更的操作,会引发ConcurrentModificationException - 该方法执行时,map 处于锁住状态(
put()是同步的),所以无需额外加锁,但也不该做耗时操作
泛型与线程安全:别默认当成生产级方案
LinkedHashMap 本身非线程安全,且泛型擦除后无法约束 key/value 类型安全。原型可以跑通,但往里塞 Integer 和取 String 会到运行时才崩。
- 如果多个线程并发读写,必须外包一层
Collections.synchronizedMap(),或者改用ConcurrentHashMap+ 手动 LRU(但那就不是“基于LinkedHashMap”了) - 泛型建议显式声明:
new LinkedHashMap<k v>(initialCapacity, 0.75f, true)</k>,避免裸类型 - 构造参数中负载因子设为
0.75f是常规选择;设太小浪费空间,太大增加哈希冲突概率,影响get()性能
测试时容易漏掉“访问更新顺序”这个关键路径
只测 put() 后 get() 能取到值,不代表 LRU 生效。真正要验证的是:当容量满后,**最近没被访问过的项是否真的被踢掉了**。
- 测试步骤应包含:插入 A/B/C(容量=2)→
get(A)→ 插入 D → 检查containsKey(B)应为false,containsKey(A)和containsKey(D)为true - 别用
keySet().toArray()看顺序——它返回的是无序数组;要用keySet()的迭代器遍历,或转成ArrayList后断言顺序 - JDK 8+ 中,
LinkedHashMap的迭代顺序就是访问顺序(当accessOrder=true),这是唯一可靠的观察方式
LinkedHashMap 原型就只是起点。









