LRU缓存实现(Java) - 编程一生

LRU缓存实现(Java) - 编程一生

💡 原文中文,约10200字,阅读约需25分钟。
📝

内容提要

本文介绍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。

🏷️

标签

➡️

继续阅读