图解LRU算法

article/2025/9/29 18:03:38

目录

一、什么是LRU算法?

二、基于双向链表+Map实现LRU算法

        1.  用双向链表看成cache缓存, 数据存放在链表上的每个节点上。

       2.  用Map记录访问cache的历史, 只要访问了 cache就将节点放置Map里。

       3.  图解移动节点和淘汰策略过程

                        

三、完整代码

四、借助LinkedHashMap实现


一、什么是LRU算法?

           LRU 全称Least Recently Used, LRU算法是一种操作系统层面上的页面置换算法,将最近最久未使用的页面进行淘汰。

二、基于双向链表+Map实现LRU算法

         为什么选用双向链表做cache?

          思想:    将多次访问的节点移到头节点,将少访问的节点移到尾节点。

          由于双向链表的自身特性,它能够很方便地找到上一个节点和下一个节点,对节点的操作相对来说比较容易。单向的不能找到上一个节点,在操作时不能提供当前节点的上一个节点遍历。

        1.  用双向链表看成cache缓存, 数据存放在链表上的每个节点上。

             在使用cache时,主要有2种操作,一种是向cache里添加数据, 另一种是从cache里获取数据。

             对于get操作: 

                    1) 如果map 里不存在,那么直接返回null。

                    2) 如果map里存在,找到将该节点移到到双向链表的表头。

            对于put操作:

                   1)  先判断用上述的get操作获取到node, 如果node不存在,接着通过 history.size()==capcity 判断容量是否已满,如果满了,就将尾节点从map 中移除,同时清除掉双向链表中的尾节点,如果没有满,那么插入到Head节点和Head的next节点之间,Head节点不存储元素。

                  2) 如果node存在,那么就cache(双向链表) 中的对应的node移到链表头。

       2.  用Map记录访问cache的历史, 只要访问了 cache就将节点放置Map里。

       3.  图解移动节点和淘汰策略过程

断掉node节点:

node.pre.next=node.next

node.next.pre=node.pre

将node插入链表头, 因为head节点不存元素,所以是插入到head和head.next节点之间。

node.next=head.next

node.pre=head

head.next=node

head.next.pre=node

画一下图,我们就会发现双向链表的优点体现出来了

三、完整代码

package sort;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author bingbing* @date 2021/5/7 0007 9:58*/
public class LRUCache<V> {private int capacity = 1024;private static Map<String, Node> history = new ConcurrentHashMap<>();Node head;Node tail;public LRUCache(int capacity) {this();this.capacity = capacity;}public LRUCache() {head = new Node();tail = new Node();head.next = tail;head.pre = null;tail.pre = head;tail.next = null;}private V get(String key) {Node node = history.get(key);if (null == node) {return null;}// 将节点移动表头node.pre.next = node.next;node.next.pre = node.pre;node.next=head.next;head.next.pre=node;head.next=node;node.pre=head;return (V) node;}private void put(String key, V value) {Node node = history.get(key);if (null == node) {// 判断容量是否已满if (history.size() == capacity) {history.remove(tail.pre.key);tail.pre = tail.next;tail.next = null;tail = tail.pre;}}// 插入到表头node = (Node) value;history.put(key, node);node.next = head.next;head.next.pre = node;head.next = node;node.pre = head;}static class Node<k, v> {private k key;private v value;Node<k, v> pre;Node<k, v> next;public Node(k key, v value) {this.key = key;this.value = value;}public Node() {}}public static void main(String[] args) {LRUCache<Node> cache = new LRUCache<>(4);Node<String, Integer> node1 = new Node<String, Integer>("k1", 1);Node<String, Integer> node2 = new Node<String, Integer>("k2", 2);Node<String, Integer> node3 = new Node<String, Integer>("k3", 3);Node<String, Integer> node4 = new Node<String, Integer>("k4", 4);Node<String, Integer> node5 = new Node<String, Integer>("k5", 5);cache.put("k1", node1);cache.put("k2", node2);cache.put("k3", node3);cache.put("k4", node4);
//        cache.get("k1");cache.put("k5", node5);Node node = cache.get("k1");System.out.println(node);}}

效果演示:

       1) 注释掉 cache.get("k1"), 从cache中拿k1, 查看结果

null

      2)  放开  cache.get("k1"), 从cache中拿k1, 查看结果

sort.LRUCache$Node@14ae5a5

第一次的容量只有4,按照最少使用规则,那么就会淘汰k1, 因此第一次测试结果为null, 第二次测试因为重新获取了k1, k1被移到了头节点,淘汰的是k2, 因此能够拿到k1。

四、借助LinkedHashMap实现

        LinkedHashMap有一个参数为acessOrder, 如果为true时,表示的是访问的时候,链表里的节点会重新排序。

    public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) {super(initialCapacity, loadFactor);this.accessOrder = accessOrder;}

        设置accessOrder参数为true,

      LinkedHashMap  lruItem = new LinkedHashMap<String, String>(16, 0.75f, true);

        使用get方法先访问1, 观察打印结果:

package com.bing.sh.cache;import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.LinkedHashMap;public class LRUTest {Logger logger = LoggerFactory.getLogger(LRUTest.class);@Testpublic void testLRUCache() {LinkedHashMap<Integer, Integer> linkedHashMap = new LinkedHashMap<Integer, Integer>(16, 0.75F, true);linkedHashMap.put(1, 1);linkedHashMap.put(2, 2);linkedHashMap.put(3, 3);logger.info(linkedHashMap.get(1).toString());linkedHashMap.entrySet().forEach((key) -> {System.out.println(key);});}
}

        1 放入到了末尾,我们可以将其倒置使用,就是一个LRU的实例了! 

        LRU的实现交给LinkedHashMap,LRU淘汰策略需要根据removeEldestEntry()方法为true时触发,LinkedHashMap的removeEledstEntry方法返回为False,因此我们只需要重写removeEldestEntry()方法, 自定义剔除的条件,即可触发LRU策略。

    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {return false;}

        我们在初始化的时候指定size 为capacity+1,size为map的实际初始化大小,如果 size> capacity 表示map已经放满了 ,需要开始触发LRU淘汰策略。

package com.bing.sh.core.collection.map;import java.util.LinkedHashMap;
import java.util.Map;public class FixedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {/*** 容量,超过容量则自动删除末尾元素*/private int capacity;private static final long serialVersionUID = -1246833412990782934L;public FixedLinkedHashMap(int capacity) {super(capacity + 1, 1.0F, true);this.capacity = capacity;}public void setCapacity(int capacity) {this.capacity = capacity;}public int getCapacity() {return capacity;}@Overrideprotected boolean removeEldestEntry(Map.Entry<K, V> eldest) {return size() > this.capacity;}
}

        触发的时候是在put()方法之后,可以跟踪源码, put 后会调用一个afterNodeInsertion方法:

    void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.Entry<K,V> first;if (evict && (first = head) != null && removeEldestEntry(first)) {K key = first.key;removeNode(hash(key), key, null, false, true);}}

removeNode()方法,是先通过key和hash找到目标的node: 

    final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {Node<K,V>[] tab; Node<K,V> p; int n, index;if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {Node<K,V> node = null, e; K k; V v;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))node = p;else if ((e = p.next) != null) {if (p instanceof TreeNode)node = ((TreeNode<K,V>)p).getTreeNode(hash, key);else {do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);}}if (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {if (node instanceof TreeNode)((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);else if (node == p)tab[index] = node.next;elsep.next = node.next;++modCount;--size;afterNodeRemoval(node);return node;}}return null;}

 最终移除掉淘汰的节点node:

参考: 

 全面讲解LRU算法_Apple_Web的博客-CSDN博客_lru算法

 LRU缓存算法的实现_唯一昵称真难的博客-CSDN博客_lru算法


http://chatgpt.dhexx.cn/article/o24dYdOs.shtml

相关文章

mysql lru_浅析MySQL的lru链表

一、简述传统的LRU链表 LRU&#xff1a;Least Recently Used 相信大家对LRU链表是不陌生的&#xff0c;它算是一种基础的数据结构吧&#xff0c;而且想必面试时也被问到过什么是LRU链表&#xff0c;甚至是让你手写一个LRU链表。 想必你已经知道了MySQL的Buffer Pool机制以及MyS…

LRU实现算法

转载自&#xff1a;https://www.cnblogs.com/Dhouse/p/8615481.html 四种实现方式 LRU 1.1. 原理 LRU&#xff08;Least recently used&#xff0c;最近最少使用&#xff09;算法根据数据的历史访问记录来进行淘汰数据&#xff0c;其核心思想是“如果数据最近被访问过&#x…

Redis LRU算法

一、配置Redis内存淘汰策略 maxmemory 100mbmaxmemory-policy allkeys-lrumaxmemory-samples 5注意&#xff1a;Redis的LRU算法并非完整的实现&#xff0c;而是近似LRU的算法&#xff0c;详细介绍点击这里 二、LRU实现原理 1、双向链表 哈希表 1、哈希表&#xff1a;查找快&…

LRU链表介绍

文章目录 1. 简介2. LRU 组织 2.1 LRU 链表2.2 LRU Cache2.3 LRU 移动操作 2.3.1 page 加入 LRU2.3.2 其他 LRU 移动操作3. LRU 回收 3.1 LRU 更新3.2 Swappiness3.3 反向映射3.4 代码实现 3.4.1 struct scan_control3.4.2 shrink_node()3.4.3 shrink_list()3.4.4 shrink_acti…

LRU页面回收

内存回收算法总是会在一定的时间将一些内存回收&#xff0c; 内存回收算法是通过LRU链表对page页面进行管理的&#xff0c;对于那些新的页面会将其插入到LRU链表头&#xff0c;回收时将返回LRU链表末尾的元素&#xff0c;代表老化程度最高的页面 基本数据结构 typedef struct…

利用数组实现lru

LRU主要包含两个函数&#xff0c;第一个插入一个页面&#xff0c;第二个获得一个页面 主要思路如下&#xff0c;当插入页面的时候&#xff0c;所有的页面向后移动一个单位&#xff08;若果多出来一个元素舍弃掉&#xff09;&#xff0c;然后把这个页面放到数组首元素 当获得一…

什么是LRU(最近最少使用)算法?

一、什么是LRU&#xff1f; LRU&#xff08;Least Recently Used&#xff09;&#xff0c;最近最少使用。 是一种【内存管理】算法。 LRU算法基于一种假设&#xff1a; 长期不被使用的数据&#xff0c;在未来被用到的几率也不大。因此&#xff0c;当数据所占内存达到一定阈值时…

什么是LRU算法

什么是LRU LRU 英文全称&#xff08;Least recently used&#xff0c;最近最少使用&#xff09;属于典型的内存管理算法。 内存管理的一种页面置换算法&#xff0c;对于在内存中但又不用的数据块&#xff08;内存块&#xff09;叫做LRU&#xff0c;操作系统会根据哪些数据属于…

LRU缓存实现与原理

概念 LRU是 Least Recently Used 的缩写&#xff0c;即最近最少使用页面置换算法&#xff0c;是为虚拟页式存储管理服务的&#xff0c;是根据页面调入内存后的使用情况进行决策了。由于无法预测各页面将来的使用情况&#xff0c;只能利用“最近的过去”作为“最近的将来”的近似…

LRU算法的详细介绍与实现

1.背景 LRU&#xff08;least recently used-最近最少使用算法&#xff09;&#xff0c;是一种内存数据淘汰策略&#xff0c;使用常见是当内存不足时&#xff0c;需要淘汰最近最少使用的数据。LRU常用语缓存系统的淘汰策略。 2.LRU原理 LRU最早实在操作系统接触到这个算法的…

LRU原来如此简单

文章目录 前言一、LRU是什么&#xff1f;二、LFU是什么&#xff1f;三、LRU和LFU的比较四、LFU代码实现&#xff08;看懂LFU就自然懂了LRU了&#xff09;1、LFU类2、Node类3、测试 写在最后&#xff0c;感谢点赞关注收藏转发 前言 现在缓存技术在项目中随处可见&#xff0c;但…

LRU算法详解

概念理解 1.LRU是Least Recently Used的缩写&#xff0c;即最近最少使用页面置换算法&#xff0c;是为虚拟页式存储管理服务的&#xff0c;是根据页面调入内存后的使用情况进行决策了。由于无法预测各页面将来的使用情况&#xff0c;只能利用“最近的过去”作为“最近的将来”的…

LRU算法

1.什么是LRU算法 LRU算法又称最近最少使用算法&#xff0c;它的基本思想是长期不被使用的数据&#xff0c;在未来被用到的几率也不大&#xff0c;所以当新的数据进来时我们可以优先把这些数据替换掉。 在LRU算法中&#xff0c;使用了一种有趣的数据结构&#xff0c;称为哈希链…

巧用 NGINX 实现大规模分布式集群的高可用性

原文作者&#xff1a;陶辉 原文链接&#xff1a;巧用 NGINX 实现大规模分布式集群的高可用性 - NGINX开源社区 转载来源&#xff1a;NGINX开源社区 本文是我对2019年GOPS深圳站演讲的文字整理。这里我希望带给各位读者的是&#xff0c;如何站在整个互联网背景下系统化地理解Ngi…

朱邦复

朱邦复 求助编辑百科名片 朱邦复&#xff0c;仓颉输入法的发明人&#xff0c;现任香港上市公司文化传信集团的副主席。湖北省黄冈县人。为中文终端机、仓颉输入法、汉卡的发明人。由于其对中文电脑发展的众多贡献&#xff0c;台湾及香港地区的华人誉其为“中文电脑之父”、“中…

TCP/IP的底层队列实现原理

个人博客请访问 http://www.x0100.top 自从上次学习了TCP/IP的拥塞控制算法后&#xff0c;我越发想要更加深入的了解TCP/IP的一些底层原理&#xff0c;搜索了很多网络上的资料&#xff0c;看到了陶辉大神关于高性能网络编程的专栏&#xff0c;收益颇多。今天就总结一下&#…

从码农到工程师:看一下这6点!

作者&#xff1a;陶辉笔记来源&#xff1a;http://www.taohui.pub 许多程序员自称码农&#xff0c;因为每天事情总也做不完&#xff0c;而这些工作也没有给自己带来职业上的提升&#xff0c;总在原地打转&#xff0c;自己的工作似乎随时可被新人替换&#xff0c;可有可无。于是…

Nginx五大类变量详解

Nginx变量详解 文章目录 Nginx变量详解一、HTTP请求相关的变量二、TCP连接相关的变量三、Nginx处理请求过程中产生的变量四、发送HTTP响应时相关的变量五、Nginx系统变量 为了方便记忆呢&#xff0c;我把nginx的全部变量分为5种&#xff0c;详情见下图 本文内容取自极客时间陶辉…

开复博士见面会

CSDN的CTO俱乐部成立一年多来&#xff0c;做过很多次活动了。我只参加了两次&#xff0c;第二次就是开复博士的创新工厂介绍会。我对创新工厂兴趣并不大&#xff0c;但很想近距离接触一下这位声名远播的演讲者和布道者。 曾经和一个&#xff36;&#xff30;的会议上&#xff…

Nginx核心知识100讲学习笔记(陶辉)Nginx架构基础(一)

(转载&#xff0c;非常不错的文章) 一、Nginx的请求处理流程进程结构 1、Nginx的请求处理流程 2、Nginx的进程结构 3、进程作用 1、Master进程 1、是进行work进程的监控管理的2、看看work进程是否正常工作需不需要进行热部署、需不需要重新载入配置文件 2、Cache manager …