linkedhashmap 可通过 accessorder=true 和自定义 timedvalue 配合 removeeldestentry() 实现写入驱动的 lru+ttl 过期,适用于低并发、读多写少场景;需规避 null 值、可变对象及线程安全问题。

LinkedHashMap 本身不支持自动过期(TTL),但它可以作为基础结构,配合时间戳 + removeEldestEntry() 实现“写入驱动”的轻量过期淘汰——适合低并发、读多写少、对过期精度要求不苛刻的本地缓存场景,比如配置快照、会话元数据、模板渲染参数等。
启用访问顺序并记录时间戳
必须设 accessOrder = true,否则 get() 不会更新顺序,过期判断就失去依据。同时,不能直接存原始 value,而要包装成带时间戳的容器(如自定义 Entry 或使用 Map.Entry 包装)。
- 构造时用三参数:
new LinkedHashMap<k timedvalue>>(initCap, 0.75f, true)</k> -
TimedValue类需包含value和accessTime(推荐用System.nanoTime()避免系统时间回拨) - 每次
put()时新建TimedValue;每次get()后手动更新对应 value 的accessTime
在 removeEldestEntry 中检查过期时间
该方法只在 put() 后触发,因此过期淘汰是被动的、依赖写操作的。它不能替代定时轮询,但足够应对“写入后自然清理”的需求。
- 重写方法中,先判空:
if (eldest == null || eldest.getValue() == null) return false; - 再取
eldest.getValue().accessTime,与当前时间比较:return System.nanoTime() - eldest.getValue().accessTime > expiryNanos; - 注意:这里淘汰的是「最久未访问且已过期」的项,不是按插入时间——这正是 LRU+TTL 的语义
规避 null 值和可变对象陷阱
业务中若允许 key 或 value 为 null,会干扰过期逻辑判断和命中检测。
- 禁止
put(key, null):统一用Optional<v></v>包装 value,或约定 value 不为 null - value 若为集合或 Bean,返回前做不可变封装(如
Collections.unmodifiableList),防止外部修改污染缓存状态 - key 为自定义对象时,
equals()和hashCode()必须稳定且基于不可变字段
不适用于高并发或强时效场景
这个方案是轻量级的折中解法,有明确边界:
- 非线程安全:并发
put可能导致ConcurrentModificationException或过期判断错乱;可用ReentrantLock手动加锁,但别用Collections.synchronizedMap()——它不同步removeEldestEntry - 无主动清理:过期项只有在新写入时才可能被踢出,长期只读的过期 key 会滞留
- 精度有限:
System.nanoTime()是相对值,适合毫秒/秒级 TTL;微秒级或需要绝对时间点的,请换用 Caffeine 或自研定时器











