必须启用 accessorder=true 才是真正的 lru,否则为 fifo;removeeldestentry 判断容量应考虑新增场景,避免多存一项;并发环境下 concurrenthashmap 无法替代 linkedhashmap 实现严格 lru。

为什么不能直接用 LinkedHashMap 的 removeEldestEntry 就完事?
能用,但默认行为只在 put 时触发淘汰,get 不会自动把访问项移到尾部——也就是说,不开启访问顺序模式,它就不是真正的 LRU,而是 FIFO。
必须在构造时传入第三个参数 true,启用 accessOrder = true:
new LinkedHashMap<K, V>(initialCapacity, loadFactor, true)
-
get操作会触发节点移动到链表尾(最新访问) -
put也会更新位置,同时可能触发removeEldestEntry - 重写
removeEldestEntry时,返回true才会删除最老项(即头结点)
手写双向链表 + HashMap 实现时,get 和 put 哪里最容易漏掉「移位」?
核心陷阱是:只改了 value,没调整节点在链表里的位置。LRU 要求每次访问都刷新“新鲜度”,否则刚 get 过的项下次可能被误删。
-
get必须:查哈希表 → 取出节点 → 从原位置解绑 → 插入链表尾 → 返回 value -
put需分情况:key 已存在 → 更新 value + 移到尾;key 不存在 → 新建节点插尾 + 若超容则删头 - 删头操作前,务必同步从
HashMap中remove对应 key,否则内存泄漏
LinkedHashMap 实现 LRU 时,removeEldestEntry 怎么写才不踩容量边界坑?
很多人直接写 return size() > capacity,看似合理,但 size() 是当前大小,而 put 还没完成插入,此时判断会少算 1 —— 导致实际存了 capacity + 1 个元素才删。
立即学习“Java免费学习笔记(深入)”;
- 正确写法是判断
size() > capacity之前,先确认这次是新增(非替换),可用!containsKey(key)辅助判断 - 更稳妥的是在
put后统一处理:重写put方法,在调用父类put后再检查并删老项 - 注意:
LinkedHashMap的size()包含所有 entry,包括刚 put 进去还没触发淘汰的那个
Java 8 的 ConcurrentHashMap 能不能套进 LRU?
不能直接套。因为 ConcurrentHashMap 不保证遍历顺序,也不提供类似 LinkedHashMap 的节点位置控制能力;它和双向链表无法原子协同更新。
- 并发 LRU 必须自己加锁或用
ReentrantLock控制 head/tail 指针 + map 的读写 - 若用
ConcurrentHashMap存 value,仍需额外结构维护访问顺序(比如时间戳字段 + 定期清理),性能和准确性都下降 - 高并发场景下,简单
synchronized块配LinkedHashMap反而比强行塞ConcurrentHashMap更可控
真正麻烦的不是结构,是「访问顺序」和「并发修改」这两个需求天然冲突——只要要求严格 LRU 语义,就绕不开对链表操作的串行化。









