LRU CaChe(内存替换算法)

六、LURCache

0、LUR Cache概念

LRU是Least Recently Used的缩写,意思是最近最少使用,它是一种Cache替换算法。 什么是Cache?狭义的Cache指的是位于CPU和主存间的快速RAM,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。广义上的Cache指的是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构。除了CPU与主存之间有Cache,内存与硬盘之间也有Cache,乃至在硬盘与网络之间也有某种意义上的Cache── 称为Internet临时文件夹或网络内容缓存等。Cache的容量有限,因此当Cache的容量用完后,而又有新的内容需要添加进来时,就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实, LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。

2、LRU Cache的实现

实现LRU Cache的方法和思路很多,但是要保持高效实现O(1)的put和get,那么使用双向链表和哈希表的搭配是最高效和经典的。使用双向链表是因为双向链表可以实现任意位置O(1)的插入和删除,使用哈希表是因为哈希表的增删查改也是O(1)。

image.png

3、JDK中类似LRUCahe的数据结构LinkedHashMap

参数说明:

  1. initialCapacity 初始容量大小:使用无参构造方法时,此值默认是16。
  2. loadFactor 加载因子:使用无参构造方法时,此值默认是 0.75f。
  3. accessOrder: false表示基于插入顺序;true表示基于访问顺序。
public static void main(String[] args) {// 当accessOrder为false时:Map<String, String> map = new LinkedHashMap<>(16,0.75f,false);map.put("1", "a");map.put("2", "b");map.put("4", "e");map.put("3", "c");System.out.println(map);
}
// 输出结果:
// {1=a, 2=b, 4=e, 3=c}
// 以上结果按照插入顺序进行打印
 public static void main(String[] args) { Map map = new LinkedHashMap<>(16,0.75f,true); map.put("1", "a"); map.put("2", "b"); map.put("4", "e"); map.put("3", "c"); map.get("1"); map.get("2"); System.out.println(map); } 
/*
输出结果:
{4=e, 3=c, 1=a, 2=b}
每次使用get方法,访问数据后,会把数据放到当前双向链表的最后。当accessOrder为true时,get方法和put方法都会调用recordAccess方法使得最近使用的Entry移到双向链表的末尾;
当accessOrder为默认值false时,从源码中可以看出recordAccess方法什么也不会做。
*/

4、LRUCache结构的特点

双向链表的头节点是最近最少使用的元素,尾节点是最近最长用的节点。
LRUCache(Least Recently Used Cache)是一种常用的缓存淘汰策略,它的主要特点如下:

  1. 容量限制:LRUCache有固定的容量上限,当缓存满时需要淘汰最久未使用的数据。
  2. 快速访问:LRUCache需要能够快速地查找、插入和删除缓存项,通常使用哈希表(字典)和双向链表的组合实现。
  3. 访问顺序跟踪:LRUCache需要记录每个缓存项的访问顺序,以便快速找到最久未使用的项。
  4. 最近最少使用:当缓存满时,LRUCache会淘汰最近最少被访问的缓存项。这样可以保留最有价值的数据。
  5. 时间复杂度:基于哈希表和双向链表的LRUCache实现,可以达到平均时间复杂度O(1)的增删查操作。

典型的LRUCache实现如下:

  1. 使用一个哈希表(字典)存储键值对,便于快速查找。
  2. 使用一个双向链表维护访问顺序,最近访问的节点放在链表头部。
  3. 当缓存已满时,将链表尾部(最久未使用)的节点移除。
  4. 当访问一个缓存项时,将其移动到链表头部。
  5. 当添加一个新缓存项时,将其加到链表头部。

这种结构可以高效地实现LRU缓存的所有操作,是LRU缓存广泛使用的基础。

5、手搓LRUCaChe

import java.util.LinkedHashMap;
import java.util.Map;public class LRUCache extends LinkedHashMap<Integer, Integer>{public int capacity;public LRUCache(int capacity) {//这个的true  代表 基于访问顺序super(capacity,0.75F,true);this.capacity = capacity;}@Overridepublic Integer get(Object key) {return super.getOrDefault(key,-1);}@Overridepublic Integer put(Integer key, Integer value) {return super.put(key, value);}@Overrideprotected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {return size() > capacity;}public static void main(String[] args) {LRUCache lruCache = new LRUCache(3);lruCache.put(100,10);lruCache.put(110,11);lruCache.put(120,12);System.out.println(lruCache);System.out.println("获取元素");System.out.println(lruCache.get(110));System.out.println(lruCache);System.out.println(lruCache.get(100));System.out.println(lruCache);System.out.println("存放元素,会删除头节点,因为头节点是最近最少使用的: ");lruCache.put(999,99);System.out.println(lruCache);}public static void main3(String[] args) {LinkedHashMap<String,Integer> linkedHashMap =new LinkedHashMap<>(16,0.7f,true);linkedHashMap.put("高博",10);linkedHashMap.put("abcd",11);linkedHashMap.put("hello",12);System.out.println(linkedHashMap);System.out.println("获取元素");System.out.println(linkedHashMap.get("abcd"));System.out.println(linkedHashMap);System.out.println(linkedHashMap.get("高博"));System.out.println(linkedHashMap);}//是基于插入顺序public static void main1(String[] args) {LinkedHashMap<String,Integer> linkedHashMap =new LinkedHashMap<>(16,0.7f,false);linkedHashMap.put("高博",10);linkedHashMap.put("abcd",11);linkedHashMap.put("hello",12);System.out.println(linkedHashMap);System.out.println("获取元素");System.out.println(linkedHashMap.get("abcd"));System.out.println(linkedHashMap);}
}
import java.util.HashMap;
import java.util.Map;/*** @Author 12629* @Description:*/
public class MyLRUCache {static class DLinkNode {public int key;public int val;public DLinkNode prev;public DLinkNode next;public DLinkNode() {}public DLinkNode(int key, int val) {this.key = key;this.val = val;}@Overridepublic String toString() {return  "{ key=" + key +", val=" + val+"} ";}}public DLinkNode head;//双向链表的头节点public DLinkNode tail;//双向链表的尾巴节点public int usedSize;//代表当前双向链表当中 有效的数据个数public Map<Integer,DLinkNode> cache;//定义一个mappublic int capacity;//容量public MyLRUCache(int capacity) {this.head = new DLinkNode();this.tail = new DLinkNode();head.next = tail;tail.prev = head;cache = new HashMap<>();this.capacity = capacity;}/*** 存储元素* 1. 查找当前的这个key 是不是存储过* @param key* @param val*/public void put(int key,int val) {//1. 查找当前的这个key 是不是存储过DLinkNode node = cache.get(key);//2. 如果没有存储过if(node == null) {//2.1 需要实例化一个节点DLinkNode dLinkNode = new DLinkNode(key,val);//2.2 存储到map当中一份cache.put(key,dLinkNode);//2.3 把该节点存储到链表的尾巴addToTail(dLinkNode);usedSize++;//2.4 检查当前双向链表的有效数据个数 是不是超过了capacityif(usedSize > capacity) {//2.5 超过了,就需要移除头部的节点DLinkNode remNode = removeHead();//2.6 清楚cache当中的元素cache.remove(remNode.key);//2.7 usedSize--;usedSize--;}printNodes("put");}else {//3. 如果存储过//3.1 更新这个key对应的valuenode.val = val;//3.2 然后将该节点,移动至尾巴处【因为这个是新插入的数据】moveToTail(node);}}/*** 移除当前节点到尾巴节点* 逻辑:先删除  后添加到尾巴* @param node*/private void moveToTail(DLinkNode node) {//1. 先删除这个节点removeNode(node);//2. 添加到尾巴节点addToTail(node);}/*** 删除指定节点* @param node*/private void removeNode(DLinkNode node) {node.prev.next = node.next;node.next.prev = node.prev;}/*** 添加节点到链表的尾部* @param node*/private void addToTail(DLinkNode node) {tail.prev.next = node;node.prev = tail.prev;node.next = tail;tail.prev = node;}private DLinkNode removeHead() {DLinkNode del = head.next;head.next = del.next;del.next.prev = head;return del;}/*** 访问当前的key*   逻辑:把你访问的节点 放到尾巴* @param key* @return*/public int get(int key) {DLinkNode node = cache.get(key);if(node == null) {return -1;}//把最近 最多使用的 放到了链表的尾巴moveToTail(node);printNodes("get ");return node.val;}public void printNodes(String str) {System.out.println(str+": ");DLinkNode cur = head.next;while (cur != tail) {System.out.print(cur);cur = cur.next;}System.out.println();}public static void main(String[] args) {MyLRUCache lruCache = new MyLRUCache(3);lruCache.put(100,10);lruCache.put(110,11);lruCache.put(120,12);System.out.println("获取元素");System.out.println(lruCache.get(110));System.out.println(lruCache.get(100));System.out.println("存放元素,会删除头节点,因为头节点是最近最少使用的: ");lruCache.put(999,99);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/46788.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

统计字符串中出现指定字符的次数

题目 声明一个字符串&#xff0c;内容为‘HelloPython,HelloJava,hellophp’&#xff0c;用户从键盘录入要查询的字符&#xff08;不区分大小写&#xff09;&#xff0c;要求统计出要查找的字符在字符串中出现的次数 代码 sHelloPython,HelloJava,hellophp wordinput(请输入…

C++ 入门14:STL 容器之向量(vector)

往期回顾&#xff1a; C 入门11&#xff1a;虚函数和多态-CSDN博客 C 入门12&#xff1a;模板&#xff08;Template&#xff09;-CSDN博客 C 入门13&#xff1a;异常处理-CSDN博客 C 入门14&#xff1a;STL 容器之向量&#xff08;vector&#xff09; 一、前言 在前面文章的…

使用Python进行车牌识别

哈喽,大家好,我是木头左! 1. 车牌识别的重要性 车牌识别是计算机视觉领域的一个重要应用,它可以自动识别车辆的车牌号码。在交通管理、停车场管理、道路监控等领域,车牌识别技术具有广泛的应用前景。本文将介绍如何使用Python进行车牌识别,帮助读者掌握这一技能。 2. 车…

打开磁盘格式为NTFS的磁盘 ntfs磁盘无法打开 移动硬盘出现ntfs怎么打不开了的原因和解决方案

在日常的工作和生活中&#xff0c;移动硬盘作为我们存储和传输数据的重要工具&#xff0c;发挥着不可替代的作用。然而&#xff0c;有时候我们会遇到这样一个问题&#xff1a;移动硬盘突然显示NTFS格式&#xff0c;并且无法正常打开。面对这种情况&#xff0c;我们往往感到束手…

【Android】高端的UI,往往只需要最朴素的控件组成

【Android】高端的UI&#xff0c;往往只需要最朴素的控件组成 Android 作为全球最流行的移动操作系统之一&#xff0c;提供了一套丰富的 UI 控件&#xff0c;使开发者能够构建功能强大、交互性强的应用。本博客系列将探讨 Android UI 控件的功能&#xff0c;介绍最常见的六种控…

实战:Linux下静默安装DM达梦数据库

官方文档参考&#xff1a;安装前准备 | 达梦技术文档 静默安装DM达梦数据库&#xff0c;提前编写好安装脚本、下载好软件、配置好服务器的内核参数信息。 一、安装前的准备 1.操作系统环境检测 #查看服务器操作系统版本和型号 cat /etc/system-release cat /etc/os-releas…

【系统架构设计师】十一、系统架构设计(层次架构风格|MVC|面向服务的架构风格|ESB)

目录 五、层次架构风格 5.1 两层C/S架构 5.2 三层C/S架构 5.3 三层B/S架构 5.4 MVC架构 5.5 MVP架构 5.6 MVVM架构 六、面向服务的架构风格 6.1 SOA特征 6.2 Web Service 6.2.1 关键技术 6.2.2 WEB Service 6.3 企业服务总线ESB 相关推荐 历年真题练习 五、层次…

【MAUI】生命周期

.NET Multi-platform App UI (.NET MAUI) 应用通常有四种执行状态&#xff1a;“未运行”、“运行中”、“已停用”和“已停止”。 当应用从未运行状态转换为运行状态、从运行状态转换为已停用状态、从已停用状态转换为已停止状态、从已停止状态转换为运行状态&#xff0c;以及…

Java并发编程之如何正确的停止线程

在Java线程状态转换中&#xff0c;我们知道&#xff0c;线程最终的命运是Terminated&#xff0c;当然&#xff0c;也有永不停止一直干活的线程&#xff08;除非断电&#xff09;。线程的停止&#xff0c;正常来说是线程运行到结束&#xff0c;但也有程序出错或是用户关闭程序等…

new mars3d.graphic.FixedRoute({的position长度超过一百条浏览器会卡死的解决方案

问题场景描述&#xff1a; FixedRoute的position数据已经很精细时&#xff0c;会导致卡死的问题 解决方案&#xff1a; 1. 数据已经很精细时&#xff0c;可以不用autoSurfaceHeight来计算&#xff0c;如果非要用&#xff0c;可以加个minDistance: 200参数。 fixedRoute.auto…

ChatGPT 深度解析:技术驱动的智能对话

在当今科技飞速发展的时代&#xff0c;ChatGPT 无疑成为了最耀眼的明星之一。它以其令人惊叹的智能对话能力&#xff0c;引发了全球范围内的广泛关注和热议。 ChatGPT 背后的技术堪称精妙绝伦。它基于深度学习算法&#xff0c;通过对海量数据的学习和分析&#xff0c;从而能够理…

BasicSR项目(通用图像超分、修复、增强工具库)介绍

项目地址&#xff1a;https://github.com/XPixelGroup/BasicSR 文档地址&#xff1a;https://github.com/XPixelGroup/BasicSR-docs/releases BasicSR 是一个开源项目&#xff0c;旨在提供一个方便易用的图像、视频的超分、复原、增强的工具箱。BasicSR 代码库从2018年4月20日…

【Memcached】Memcached的工作原理

目录 ​编辑 第2章&#xff1a;Memcached工作原理 2.1 数据存储与访问 2.2 分布式架构 2.3 数据过期机制 第2章&#xff1a;Memcached工作原理 2.1 数据存储与访问 Memcached是一种键值存储系统&#xff0c;其中数据以键值对的形式存储。键是用于定位数据的唯一标识符&am…

MySQL架构详解

MySQL是一个广泛使用的开源关系数据库管理系统&#xff0c;以其可靠性、性能和易用性而闻名。了解MySQL的架构对于优化数据库性能、设计高效的数据库系统以及进行有效的数据库管理至关重要。本文将详细介绍MySQL的架构&#xff0c;包括其主要组件和功能。 1. 连接器&#xff0…

libyaml库的交叉编译

目录 1.Ubuntu环境中安装libyaml库 2.交叉编译 3.success 1.Ubuntu环境中安装libyaml库 官方地址&#xff1a;https://pyyaml.org/wiki/LibYAML 下载路径&#xff1a;http://pyyaml.org/download/libyaml/yaml-0.2.5.tar.gz 2.交叉编译 官方的下载路径为/usr/local下&am…

【unity实战】使用unity制作一个红点系统

前言 注意&#xff0c;本文是本人的学习笔记记录&#xff0c;这里先记录基本的代码&#xff0c;后面用到了再回来进行实现和整理 素材 https://assetstore.unity.com/packages/2d/gui/icons/2d-simple-ui-pack-218050 框架&#xff1a; RedPointSystem.cs using System.…

PHP全功能微信投票迷你平台系统小程序源码

&#x1f525;让决策变得超简单&#xff01;&#x1f389; &#x1f680;【一键创建&#xff0c;秒速启动】 嘿小伙伴们&#xff0c;你还在为组织投票而手忙脚乱吗&#xff1f;来试试这款全功能投票迷你微信小程序吧&#xff01;只需轻轻一点&#xff0c;无论是班级选举、社团…

Java创建对象除了new还有别的什么方式?

通过反射创建对象&#xff1a;通过 Java 的反射机制可以在运行时动态地创建对象。可以使用 Class 类的 newInstance() 方法或者通过 Constructor 类来创建对象 public class MyClass { public MyClass() { // Constructor } } public class Main { public…

初学者指南:如何搭建和配置 Nginx 服务器

初学者指南&#xff1a;如何搭建和配置 Nginx 服务器 Nginx 是一个高性能的 HTTP 和反向代理服务器&#xff0c;也是一个 IMAP/POP3/SMTP 代理服务器。本文将详细介绍如何在 Linux 上安装、配置和管理 Nginx 服务器。 一、安装 Nginx Nginx 可以安装在多种操作系统上&#x…

c# 依赖注入-服务的生命周期

在 C# 中&#xff0c;依赖注入服务的生命周期指的是在应用程序中管理和控制依赖项注入服务对象的生命周期的方式。常见的生命周期包括瞬态&#xff08;transient&#xff09;、作用域&#xff08;scoped&#xff09;和单例&#xff08;singleton&#xff09;三种。 瞬态&#…