如何实现LRU缓存?
面试官:来了,老弟,LRU缓存实现一下?
我:直接LinkedHashMap就好了。
面试官:不要用现有的实现,自己实现一个。
我:.....
面试官:回去等消息吧....
大家好,我是程序员学长,今天我们来聊一聊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操作时,我们从链表头开始顺序遍历。遍历的结果有两种情况。
- 如果此数据在链表中。我们遍历得到这个数据对应的结点,然后将其从这个位置移动到链表的头部,并返回这个结点对应的value。
- 如果此数据不在链表中。我们直接返回-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 ...
随机推荐
- python中进程详解
1:pdb调试:基于命令行的调试工具,非常类似gnu和gdb调试,以下是常用的调试命令: 可以python -m pdb xxx.py(你的py文件名)进入命令行调试模式 命令 简写命令 作用 bea ...
- 总结开发中基于DevExpress的Winform界面效果
DevExpress是一家全球知名的控件开发公司, DevExpress 也特指此公司出品的控件集合或某系列控件或其中某控件.我们应用最为广泛的是基于Winform的DevExpress控件组,本篇随 ...
- LAMP和LNMP环境搭建的艰辛历程
目录 1. LAMP环境的搭建 1. Apache 安装apache遇到的问题 2. mysql 登录mysql的方法 3. PHP 2. Lnmp环境的搭建 1. nginx 2. PHP 3. 配 ...
- OpenGL学习笔记(五)变换
目录 变换 向量 向量的运算 向量与标量运算 向量取反 向量加减 求向量长度 向量的单位化 向量相乘 点乘(Dot Product) 叉乘 矩阵 矩阵的加减 矩阵的数乘 矩阵相乘 矩阵与向量相乘 与单 ...
- ERROR: database "db" is being accessed by other users
执行DROP DATABASE testdb;的时候提示: ERROR: database "testdb" is being accessed by other users DE ...
- 分享我的CleanArchitecture for Razor Page项目模板
这个项目是参考和整合了jasontaylordev/CleanArchitecture 和 blazorhero/CleanArchitecture 代码基础上,重构出来的新的项目,这两个项目都是非常 ...
- JVM内存调整
JVM内存调整 先试着调整一下idea的 找到软件安装位置/bin/idea64.exe.vmoptions 给他直接整个起飞的,改成 -Xms512m -Xmx1500m 找到Java安装的位置/j ...
- 9、改善深度神经网络之正则化、Dropout正则化
首先我们理解一下,什么叫做正则化? 目的角度:防止过拟合 简单来说,正则化是一种为了减小测试误差的行为(有时候会增加训练误差).我们在构造机器学习模型时,最终目的是让模型在面对新数据的时候,可以有很好 ...
- 痞子衡嵌入式:i.MXRT中不支持DQS的FlexSPI引脚组连接Flash下载与启动注意事项
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是i.MXRT中不支持DQS的FlexSPI引脚组连接Flash下载与启动注意事项. 最近痞子衡在支持一个印度客户,这个客户项目主芯片选择 ...
- Python - typing 模块 —— Callable
前言 typing 是在 python 3.5 才有的模块 前置学习 Python 类型提示:https://www.cnblogs.com/poloyy/p/15145380.html 常用类型提示 ...