如何实现LRU缓存
大家好,我是程序员学长,今天我们来聊一聊LRU缓存问题。
Tips: LRU在计算机软件中无处不在,希望大家一定要了解透彻。
问题描述
设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
1. set(key, value):将记录(key, value)插入该结构
2. get(key):返回key对应的value值
分析问题
根据问题描述,我们可以知道LRU包含两种操作,即Set和Get操作。
对于Set操作来说,分为两种情况。
- 缓存中已经存在。把缓存中的该元素移动到缓存头部。
- 如果缓存中不存在。把该元素添加到缓存头部。如果此时缓存的大小超过限制的大小,需要删除缓存中末尾的元素。
对于Get操作来着,也分为两种情况。
- 缓存中存在。把缓存中的该元素移动到缓存头部。并返回对应的value值。
- 缓存中不存在。直接返回-1。
综上所述:对于一个LRU缓存结构来说,主要需要支持以下三种操作。
- 查找一个元素。
- 在缓存末尾删除一个元素。
- 在缓存头部添加一个元素。
所以,我们最容易想到的就是使用一个链表来实现LRU缓存。
我们可以维护一个有序的单链表,越靠近链表尾部的结点是越早访问的。
当我们进行Set操作时,我们从链表头开始顺序遍历。遍历的结果有两种情况。
- 如果此数据之前就已经被缓存在链表中,我们遍历得到这个数据对应的结点,然后将其从这个位置移动到链表的头部。
- 如果此数据不在链表中,又会分为两种情况。如果此时缓存链表没有满,我们直接将该结点插入链表头部。如果此时缓存链表已经满了,我们从链表尾部删除一个结点,然后将新的数据结点插入到链表头部。
当我们进行Get操作时,我们从链表头开始顺序遍历。遍历的结果有两种情况。
- 如果此数据之前就已经被缓存在链表中,我们遍历得到这个数据对应的结点,然后将其从这个位置移动到链表的头部。
- 如果此数据之前不在缓存中,我们直接返回-1。
下面我们来看一下代码如何实现。
class LinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.next = None
class LRUCache():
def __init__(self, capacity: int):
# 使用伪头部节点
self.capacity=capacity
self.head = LinkedNode()
self.head.next=None
self.size = 0
def get(self, key: int) -> int:
cur=self.head.next
pre=self.head
while cur!=None:
if cur.key==key:
pre.next = cur.next
cur.next = self.head.next
self.head.next = cur
break
pre=pre.next
cur=cur.next
if cur!=None:
return cur.value
else:
return -1
def put(self, key: int, value: int) -> None:
cur = self.head.next
pre = self.head
#缓存没有元素,直接添加
if cur==None:
node = LinkedNode()
node.key = key
node.value = value
self.head.next = node
self.size = self.size + 1
return
#缓存有元素,判断是否存在于缓存中
while cur!=None:
#表示已经存在
if cur.key == key:
#把该元素反正链表头部
cur.value=value
pre.next = cur.next
cur.next = self.head.next
self.head.next = cur
break
#代表当前元素时最后一个元素
if cur.next==None:
#如果此时缓存已经满了,淘汰最后一个元素
if self.size==self.capacity:
pre.next=None
self.size=self.size-1
node=LinkedNode()
node.key=key
node.value=value
node.next=self.head.next
self.head.next=node
self.size=self.size+1
break
pre = pre.next
cur=cur.next
这样我们就用链表实现了一个LRU缓存,我们接下来分析一下缓存访问的时间复杂度。对于Set来说,不管缓存有没有满,我们都需要遍历一遍链表,所以时间复杂度是O(n)。对于Get操作来说,也是需要遍历一遍链表,所以时间复杂度也是O(n)。
优化
从上面的分析,我们可以看到。如果用单链表来实现LRU,不论是Set还是Get操作,都需要遍历一遍链表,来查找当前元素是否在缓存中,时间复杂度为O(n),那我们可以优化吗?我们知道,使用hash表,我们查找元素的时间复杂度可以减低到O(1),如果我们可以用hash表,来替代上述的查找操作,那不就可以减低时间复杂度吗?根据这个逻辑,所以我们采用hash表和链表的组合方式来实现一个高效的LRU缓存。
class LinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict()
self.head = LinkedNode()
self.tail = LinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
def get(self, key: int):
#如果key不存在,直接返回-1
if key not in self.cache:
return -1
#通过hash表定位位置,然后删除,省去遍历查找过程
node = self.cache[key]
self.moveHead(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
# 如果key不存在,创建一个新的节点
node = LinkedNode(key, value)
# 添加进哈希表
self.cache[key] = node
self.addHead(node)
self.size += 1
if self.size > self.capacity:
# 如果超出容量,删除双向链表的尾部节点
removed = self.removeTail()
# 删除哈希表中对应的项
self.cache.pop(removed.key)
self.size -= 1
else:
node = self.cache[key]
node.value = value
self.moveHead(node)
def addHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def moveHead(self, node):
self.removeNode(node)
self.addHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node
总结
LRU缓存不论在工作中还是面试中,我们都会经常碰到。希望这篇文章能对你有所帮助。
今天,我们就聊到这里。更多有趣知识,请关注公众号【程序员学长】。
你知道的越多,你的思维也就越开阔,我们下期再见。
如何实现LRU缓存的更多相关文章
- LRU缓存实现(Java)
LRU Cache的LinkedHashMap实现 LRU Cache的链表+HashMap实现 LinkedHashMap的FIFO实现 调用示例 LRU是Least Recently Used 的 ...
- 转: LRU缓存介绍与实现 (Java)
引子: 我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本, ...
- volley三种基本请求图片的方式与Lru的基本使用:正常的加载+含有Lru缓存的加载+Volley控件networkImageview的使用
首先做出全局的请求队列 package com.qg.lizhanqi.myvolleydemo; import android.app.Application; import com.android ...
- 如何用LinkedHashMap实现LRU缓存算法
阿里巴巴笔试考到了LRU,一激动忘了怎么回事了..准备不充分啊.. 缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的.LRU这个算法就是把最近一次 ...
- 面试挂在了 LRU 缓存算法设计上
好吧,有人可能觉得我标题党了,但我想告诉你们的是,前阵子面试确实挂在了 RLU 缓存算法的设计上了.当时做题的时候,自己想的太多了,感觉设计一个 LRU(Least recently used) 缓存 ...
- Java集合详解5:深入理解LinkedHashMap和LRU缓存
今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存. 摘要: HashMap和双向链表合二为一即是LinkedHashMap.所谓Linke ...
- 04 | 链表(上):如何实现LRU缓存淘汰算法?
今天我们来聊聊“链表(Linked list)”这个数据结构.学习链表有什么用呢?为了回答这个问题,我们先来讨论一个经典的链表应用场景,那就是+LRU+缓存淘汰算法. 缓存是一种提高数据读取性能的技术 ...
- LRU缓存原理
LRU(Least Recently Used) LRU是近期最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象. 采用LRU算法的缓存有两种:LrhCache和DisL ...
- 链表(上):如何实现LRU缓存淘汰算法?
一.什么是链表 和数组一样,链表也是一种线性表. 从内存结构来看,链表的内存结构是不连续的内存空间,是将一组零散的内存块串联起来,从而进行数据存储的数据结构. 链表中的每一个内存块被称为节点Node. ...
- [Leetcode]146.LRU缓存机制
Leetcode难题,题目为: 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key ...
随机推荐
- Thinkphp 分页应用
$Table = M('Table'); $count = $Table ->where()->count(); $Page = new \Think\Page($count ...
- Spring Data Commons 远程命令执行漏洞(CVE-2018-1273)
影响版本 Spring Framework 5.0 to 5.0.4 Spring Framework 4.3 to 4.3.14 poc https://github.com/zhzyker/exp ...
- Django < 2.0.8 任意URL跳转漏洞(CVE-2018-14574)
影响版本 Django < 2.0.8 抓包 访问http://192.168.49.2:8000//www.example.com,即可返回是301跳转到//www.example.com
- 通过比较rgb破解滑动验证码
目标url:信用中国(广东中山)行政处罚 /** * base64转BufferedImage * * @param base64 * @return */ public static Buffere ...
- Java面向对象11——多态
多态 package oop.demon01.demon06; public class Application { public static void main(String[] a ...
- Java面向对象05——创建对象内存分析
- JIPB | 两篇连发:华中农大黄俊斌团队报道二羟基异丁酰化调控稻曲病菌致病新机制
水稻是我国重要的粮食作物,稻曲病是水稻三大病害之一,不仅造成稻米产量损失,更重要的是稻曲球中的稻曲菌素的毒性和致畸作用,给人畜健康带来严重威胁.病原菌对植物的侵袭是由病原菌的毒力和植物免疫系统相互作用 ...
- HCIA-网络层IP地址
TCP/IP 每一层关联性 网络接口层-->TYPE 上层的网络层 --> Protocol -->不同的传输层协议 DSAP SSAP IP地址 剩下的8个字节 IPV4地址 4个 ...
- Maven 手动安装JAR包到本地maven仓库后,但在项目中依旧报错找不到JAR包解决方法
本博客包含的内容: ①手动安装jar包到本地仓库: ②解决Missing artifact org.source.fastdfs:fastdfs:jar问题 .personSunflowerP { b ...
- 【笔记】SVM思想解决回归问题
使用svm思想解决回归问题 使用svm思想解决是如何解决回归问题,其中回归问题的本质就是找一条线,能够最好的拟合数据点 怎么定义拟合就是回归算法的关键,线性回归算法就是让预测的直线的MSE的值最小,对 ...