一、LRU算法介绍
LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,为虚拟页式存储管理服务。
LRU算法的提出,是基于这样一个事实:在前面几条指令中使用频繁的页面很可能在后面的几条指令中频繁使用。反过来说,已经很久没有使用的页面很可能在未来较长的一段时间内不会被用到。
这个,就是著名的局部性原理。此外,LRU算法也经常被用作缓存淘汰策略。
二、实现方式
最常见的实现是使用一个双向链表保存缓存数据,详细算法实现如下:
1、新数据插入到链表头部;
2、每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3、当链表满的时候,将链表尾部的数据丢弃。
优点:实现简单。
缺点:每次访问都需要更新链表,因此代价略高。
三、代码实现
1、版本一:
/** * 在版本一中,利用HashMap和一个简单的双向链表来实现LRU缓存 */ class LRUCache { // 双向链表节点定义 class Node { int key; int val; Node prev; Node next; } private int capacity; //保存链表的头节点和尾节点 private Node first; private Node last; private Map<Integer, Node> map; public LRUCache(int capacity) { this.capacity = capacity; map = new HashMap<>(capacity); } public int get(int key) { Node node = map.get(key); //为空返回-1 if (node == null) { return -1; } moveToHead(node); return node.val; } private void moveToHead(Node node) { if (node == first) { return; } else if (node == last) { last.prev.next = null; last = last.prev; } else { node.prev.next = node.next; node.next.prev = node.prev; } node.prev = first.prev; node.next = first; first.prev = node; first = node; } public void put(int key, int value) { Node node = map.get(key); if (node == null) { node = new Node(); node.key = key; node.val = value; if(map.size() == capacity) { removeLast(); } addToHead(node); map.put(key, node); } else { node.val = value; moveToHead(node); } } private void addToHead(Node node) { if (map.isEmpty()) { first = node; last = node; } else { node.next = first; first.prev = node; first = node; } } private void removeLast() { map.remove(last.key); Node prevNode = last.prev; if (prevNode != null) { prevNode.next = null; last = prevNode; } } @Override public String toString() { return map.keySet().toString(); } public static void main(String[] args) { LRUCache cache = new LRUCache(3); cache.put(1, 1); cache.put(2, 2); cache.put(3, 3); cache.get(1); cache.put(4, 3); System.out.println(cache); } }
打印结果: [4, 1, 3]。
可以看到这种实现方式 get 和 put 的操作时间复杂度都是O(1)的。
2、版本二:
其实JDK已经提供了一个基于HashMap和双向链表实现的数据结构,它就是LinkedHashMap,它内部维护的双向链表,可以帮助维护两种顺序:插入顺序和LRU顺序。
public class LRUCache<K,V> extends LinkedHashMap<K, V>{ //首先设定最大缓存空间 MAX_ENTRIES 为 3; private static final int MAX_ENTRIES = 3; //之后使用LinkedHashMap的构造函数将 accessOrder设置为 true,开启 LRU顺序; public LRUCache() { super(MAX_ENTRIES, 0.75f, true); } //最后覆盖removeEldestEntry()方法实现,在节点多于 MAX_ENTRIES 就会将最近最少使用的数据移除。 //因为这个函数默认返回false,不重写的话缓存爆了的时候无法删除最近最久未使用的节点 @Override protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) { //在容量超过最大允许节点数的时候返回true,使得在afterNodeInsertion函数中能执行removeNode() return size() > MAX_ENTRIES; } public static void main(String[] args) { LRUCache<Integer, Integer> cache = new LRUCache<>(); cache.put(1, 1); cache.put(2, 2); cache.put(3, 3); cache.get(1); cache.put(4, 4); System.out.println(cache.keySet()); } }
打印结果:[3, 1, 4]。
分类:
NoSQL
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
2020-05-12 关于Condition
2020-05-12 关于并发的相关概念
2020-05-12 关于wait/notify(二)
2020-05-12 关于wait/notify(一)
2020-05-12 关于Volatile(一)
2020-05-12 关于Volatile(二)