linkedhashmap 能直接支持 lru 是因为它内置访问顺序模式(accessorder = true),使 get() 和 put() 操作自动将节点移至链表尾部,配合重写 removeeldestentry() 可在超容时移除头节点(最久未用项)。

为什么 LinkedHashMap 能直接支持 LRU?
因为 LinkedHashMap 内置了访问顺序模式(accessOrder = true),它会在每次 get() 或 put() 后把对应节点移到链表尾部;而默认的插入顺序模式(accessOrder = false)只按插入顺序维护——LRU 正好需要「最近访问在尾,最久未用在头」的结构。
关键点在于:必须用带四个参数的构造函数显式启用访问顺序,否则即使重写 removeEldestEntry() 也无效。
示例初始化:
Map<Integer, String> cache = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) {
return size() > 10; // 容量超 10 时移除头节点(最久未用)
}
};
如何确保 put() 和 get() 都触发 LRU 更新?
默认情况下,get() 在 accessOrder = true 下会更新位置,但 put() 的行为需注意:
立即学习“Java免费学习笔记(深入)”;
- 如果 key 已存在,
put()会覆盖 value 并将该 entry 移至尾部(符合 LRU) - 如果 key 不存在,
put()插入新 entry 到尾部(也符合逻辑) - 但若用
putIfAbsent()或computeIfAbsent(),它们不会触发位置更新 —— 这些方法绕过了 LinkedHashMap 的 access-order 机制
所以实际使用中,应统一走 put() + get(),避免混用非标准更新方法。
removeEldestEntry() 的触发时机和常见陷阱
removeEldestEntry() 只在每次 put()(含 putAll())之后被调用一次,**不会在 get() 后触发**。这意味着:
- 缓存大小只在写入时检查并收缩,读多写少场景下可能短暂超限
- 返回
true时,LinkedHashMap 会自动移除头节点(即最久未用项) - 不要在该方法里调用
size()以外的 map 操作(如get()、remove()),会引发ConcurrentModificationException - 若想支持动态容量,可在外部封装一层,把容量作为成员变量传入匿名类,或改用静态内部类实现
线程安全问题怎么处理?
LinkedHashMap 本身不保证线程安全。多线程环境下直接使用会导致:
-
ConcurrentModificationException(遍历时被其他线程修改) - 数据错乱(如两个线程同时
put()导致链表断裂)
常见应对方式:
- 用
Collections.synchronizedMap(new LinkedHashMap(...))—— 简单但粒度粗(整个 map 锁),高并发下性能差 - 自己加
synchronized块包裹所有操作(get/put/size)—— 更可控,但易漏 - 改用
ConcurrentHashMap+ 手动维护访问顺序(复杂,一般不推荐)
真正需要高性能且线程安全的 LRU 缓存,建议直接用 caffeine 或 guava Cache,它们底层已做精细优化。
LinkedHashMap 实现 LRU 的核心就三件事:开 accessOrder、重写 removeEldestEntry、避开非标准更新方法——其余都是边界细节。










