accessorder = true 是 lru 的关键,因它使 get()/put() 触发节点移至链表尾;removeeldestentry() 需预留缓冲位防误删;并发场景下 linkedhashmap 不安全,推荐 concurrenthashmap + 队列手动维护顺序。

为什么 LinkedHashMap 的 accessOrder = true 是 LRU 的关键
因为默认的 LinkedHashMap 只按插入顺序维护链表,而 LRU 要求每次 get() 或 put() 都把对应节点移到链表尾部——这只有开启 accessOrder = true 才能触发。不设这个参数,无论怎么访问,顺序都不会变,缓存淘汰就完全失效。
实操建议:
- 必须在构造时传入三参数版本:
new LinkedHashMap(initialCapacity, loadFactor, true) -
loadFactor建议保持默认0.75f,调高易触发扩容,调低浪费空间 - 别用两参数构造后“再设” accessOrder——它不可变,构造完就定死了
如何安全重写 removeEldestEntry() 控制缓存大小
这是 LRU 缓存自动淘汰的核心钩子,但很多人直接在里面写 return size() > maxSize,结果发现缓存始终只存 1 个元素——因为 put() 插入新 entry 后才调这个方法,此时 size 已经是 maxSize + 1,删掉的是刚插进来的那个。
正确做法是预留一个“缓冲位”:
- 让
removeEldestEntry()返回size() > maxSize,但初始化时把maxSize设为你要的实际容量 - 更稳妥的是在
put()前先检查 size,超限时手动remove()最老 entry(需配合keySet().iterator().next()) - 注意:该方法在
put()和putAll()中被调用,但get()不触发它——所以它只管“新增导致超限”,不管“访问引发的置换”
get() 触发排序但不改变 value?小心并发和包装类陷阱
get() 在 accessOrder = true 下会把命中节点移到链表尾,但不会调用 removeEldestEntry(),也不会复制或重新包装 value。问题常出在 value 本身可变:
- 如果 value 是
ArrayList或自定义对象,get()返回的是原始引用,外部修改会影响缓存内容 - 多线程环境下,
LinkedHashMap本身不保证线程安全,get()+put()组合可能产生竞态——比如两个线程同时get()同一 key,都触发 move-to-end,但链表结构可能错乱 - 避免用
Integer等小数值作 value 并期望 == 比较,缓存里存的是装箱对象,== 判断不可靠
替代方案:ConcurrentHashMap + Queue 手动维护顺序更可控吗?
当缓存规模大、并发高、或需要精确控制淘汰逻辑(比如带权重、过期时间),硬套 LinkedHashMap 反而容易翻车。它的迭代器弱一致性,removeEldestEntry() 无法抛异常,扩容时链表重排也可能干扰 LRU 行为。
这时不如拆开实现:
- 用
ConcurrentHashMap存数据,保障读写并发安全 - 用
ConcurrentLinkedQueue或ArrayDeque(单线程场景)单独记录访问顺序,get()时 remove + addLast - 淘汰时从队列头取 key,查 map 删除——虽然代码多几行,但每步行为清晰,调试和加监控都方便
真正难的不是“怎么让顺序动起来”,而是“动的时候不丢数据、不卡主线程、不破坏并发语义”。LinkedHashMap 的 LRU 是玩具级起点,生产环境往往得自己捏轮子。







