linkedhashmap 是 lru 最简实现基础,因其启用访问顺序模式(构造时 third param=true)后,get/put 自动将节点移至尾部,再重写 removeeldestentry() 即可自动淘汰头节点。

为什么 LinkedHashMap 是 LRU 最简实现基础
因为它的迭代顺序天然支持「访问顺序」,不用自己维护链表或时间戳。普通 HashMap 无序,TreeMap 按 key 排序,都不适合 LRU 的「最近访问优先淘汰」逻辑。
关键在构造时传入 true 作为第三个参数:new LinkedHashMap(capacity, 0.75f, true) —— 这个 true 表示启用访问顺序模式,每次 get() 或 put() 都会把对应 entry 移到链表尾部。
- 不传
true(默认false):只按插入顺序排列,get()不会改变位置,无法支撑 LRU - 容量和负载因子建议显式指定,避免因扩容触发重哈希打乱原有顺序
- 注意:仅靠构造参数还不够,还得重写
removeEldestEntry()才能自动淘汰
如何让 LinkedHashMap 自动删除最老项
靠重写 removeEldestEntry() 方法。这个钩子在每次 put() 后被调用,返回 true 就删掉头节点(即最久未用项)。
典型写法是判断 size 是否超限:
立即学习“Java免费学习笔记(深入)”;
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > MAX_SIZE;
}
-
eldest参数是当前链表头部的 entry,也就是最久未访问的项 - 不要在里面做复杂操作(如日志、IO),它在
put()调用栈里,影响性能 - 如果想支持「空闲超时」或「绝对过期」,
LinkedHashMap本身做不到,得换ConcurrentHashMap + ScheduledExecutor或用Caffeine
get() 和 put() 对 LRU 顺序的影响差异
get() 触发访问顺序更新的前提是启用了访问模式(构造时第三个参数为 true);而 put() 不仅更新值,还会把该 key 移到尾部 —— 即使是新 key,也会成为最新访问项。
- 对已存在 key 调用
put():等效于先get()再设值,顺序更新一次 - 对不存在 key 调用
put():插入新节点到尾部,不影响其他项顺序 - 调用
getOrDefault()或computeIfAbsent()也能触发顺序更新,但containsKey()不会 - 多线程下直接用非同步的
LinkedHashMap会出错,要么加锁,要么用Collections.synchronizedMap()(但注意迭代仍需手动同步)
容易被忽略的边界行为:null key / null value 和扩容抖动
LinkedHashMap 允许 null key 和 null value,但 LRU 场景下它们会干扰淘汰逻辑 —— 比如 null key 的 get() 仍会触发顺序更新,但它在 removeEldestEntry() 里难区分是否有效数据。
- 建议业务层禁止
nullkey,value 若必须为null,应在removeEldestEntry()中额外判空 - 扩容时整个链表重建,虽然顺序保持,但会短暂阻塞并增加 GC 压力;预估好初始
capacity,避免频繁扩容 - 如果缓存项有外部引用(比如 value 是大对象且被其他地方强引用),即使被
LinkedHashMap淘汰,也不会被回收 —— LRU 只管容器内引用
真正要稳,就得接受:LinkedHashMap 提供的是「轻量级 LRU 骨架」,不是生产级缓存。一有并发、过期、权重、监控需求,就得切到 Caffeine 或 Guava Cache。










