内容提要
本文介绍Java实现LRU缓存的多种方式:继承LinkedHashMap并重写removeEldestEntry;委托封装LinkedHashMap并手动同步;链表加HashMap自行实现;以及LinkedHashMap默认FIFO缓存写法及调用示例。
延伸解读
LinkedHashMap 的 accessOrder 参数是关键
文章指出,LinkedHashMap 默认按插入顺序存储,但通过构造函数传入 accessOrder=true 即可改为按访问顺序排序,最近访问的元素移到最前。这是实现 LRU 的基础。同时,removeEldestEntry 方法默认返回 false,不删除老数据,需要重写以在超出容量时删除最老元素。理解这两个机制,才能正确扩展 LinkedHashMap。
继承与委托两种扩展方式对比
文章展示了继承 LinkedHashMap 和委托封装两种方式。继承方式直接重写 removeEldestEntry,实现简单且保留了 Map 接口,多线程下可用 Collections.synchronizedMap 包装。委托方式更优雅,但未实现 Map 接口,需手动为每个方法添加 synchronized 保证线程安全。选择取决于是否需要 Map 接口以及线程安全需求。
链表+HashMap 实现更灵活但需手动维护
文章还提供了链表+HashMap 的自定义实现,通过双向链表维护访问顺序,HashMap 存储节点。put 和 get 操作会将节点移到链表头部,超出容量时删除尾部节点。该实现非线程安全,多线程环境需自行加锁。相比 LinkedHashMap,它更灵活但代码复杂,适合需要深度定制或学习原理的场景。
FIFO 缓存与 LRU 的区别
文章最后展示了 LinkedHashMap 的 FIFO 实现:不启用 accessOrder,仅重写 removeEldestEntry 在超出容量时删除最早插入的元素。与 LRU 不同,FIFO 不考虑访问频率,仅按插入顺序淘汰。调用示例中,FIFO 缓存输出顺序与 LRU 不同,体现了淘汰策略的差异。根据业务需求选择合适策略。
Q&A
Java中实现LRU缓存有哪几种常见方式?
Java中实现LRU缓存通常有两种选择:一种是使用LinkedHashMap,另一种是自己设计数据结构,使用链表+HashMap。
如何用LinkedHashMap的继承方式实现LRU缓存?
继承LinkedHashMap,在构造函数中调用super(initialCapacity, loadFactor, true)启用访问顺序,并重写removeEldestEntry方法,当size() > MAX_CACHE_SIZE时返回true以删除最老数据。
LinkedHashMap的委托方式实现LRU缓存有什么特点?
委托方式实现更加优雅,但由于没有实现Map接口,线程同步需要自己搞定,例如在put、get等方法上添加synchronized关键字。
链表+HashMap实现LRU缓存时,put和get操作是如何维护访问顺序的?
put操作:如果key不存在且缓存已满,则删除链表尾部节点(最老数据)并从HashMap中移除;然后创建或更新Entry,将其移动到链表头部,并放入HashMap。get操作:如果key存在,将对应Entry移动到链表头部并返回其值。
如何用LinkedHashMap实现一个简单的FIFO缓存?
默认情况下LinkedHashMap按添加顺序存储,只需重写removeEldestEntry方法,当size() > cacheSize时返回true即可实现FIFO缓存。
LRU缓存和FIFO缓存在淘汰策略上有什么区别?
LRU(最近最少使用)淘汰最久未被访问的数据,而FIFO(先入先出)淘汰最早添加的数据。LinkedHashMap默认按添加顺序存储,启用访问顺序后则按访问顺序存储,分别对应FIFO和LRU。