一、淘汰策略

缓存:缓存作为一种平衡高速设备与低速设备读写速度之间差异而引入的中间层,利用的是局部性原理。比如一条数据在刚被访问过只有就很可能再次被访问到,因此将其暂存到内存中的缓存中,下次访问不用读取磁盘直接从内存中的缓存读取。而内存是有限的,无法无限制的添加数据。当缓存超过设置的容量的时候,在添加缓存就需要选择性的移除无效数据。需要具体的策略判定数据是否无效。

1、FIFO

FIFO:First In First Out,先进先出,淘汰缓存中最早添加的数据。认为缓存中最早添加的数据被在此使用的可能性就越小。实现可以使用一个队列,队列中的数据严格遵循先进先出,每次内存不够用,则直接淘汰队首元素。但是很多场景下,最早添加的元素也会被经常访问,因此这类数据会频繁的进出缓存,导致性能不佳。

2、LFU

LFU:Least Frequently Used,最少使用,淘汰缓存中使用频率最低的数据。认为数据过去访问的次数越多,将来更可能被访问,因此应该尽量不被淘汰。实现上,需要维护一个记录数据访问次数的数组,每次访问数据,访问次数+1,数组就要重新排序,在淘汰时,只需淘汰访问次数最少的数据。LFU的命中率很高,缓存更有效,但是每次访问数据,都需要重排访问次数数据,排序消耗很大。另外,数据访问模式的经常变化,会导致缓存的性能下降。比如微博热点事件,在某个时间点上访问量突然加大,导致访问次数很大,过段时间可能很少访问,但是已经记录了很高的访问次数,导致该数据在缓存中很难被淘汰。

3、LRU

LRU:Least Recently Used,最近最少被使用,FIFO和LFU的这种方案。认为最近使用过的数据,在将来更可能被访问,尽量不被淘汰。相对于LFU中需要记录数据的访问次数,LRU只需要维护一个队列,队列头部保存刚被访问过的数据,队尾是最近最少未被访问的数据,缓存容量不够时候可以直接淘汰。

二、LRU实现

1、数据结构

  • 缓存字典:LRU对象需要包含一个字典,用于缓存数据。这样根据键查找值和插入新值的复杂度都是O(1)。
  • 双向链表:双向链表维护数据的最近最少使用状态。使用双向链表可以保证队尾删除节点和队头添加节点的复杂度都是O(1)

字典的键是查找值,键对应的值是双向链表对应的节点引用,这样根据字典就可以找到双向链表中的节点,进而调整双向链表中节点的顺序,更新数据的状态。

2、实现

class DLinkList:
"""定义双向链表""""
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.pre = None
self.next = None class LRUCache:
"""LRU缓存"""
def __init__(self, capacity: int):
# 初始化容量和占用大小
self.capacity = capacity
self.size = 0
self.cache = dict()
# 初始化头结点和尾节点
self.tail = DLinkList()
self.head = DLinkList()
self.tail.pre = self.head
self.head.next = self.tail def get(self, key: int) -> int:
# 未命中缓存
if key not in self.cache:
return -1
# 命中缓存修改将节点前移首部
node = self.cache[key]
self.moveToHead(node)
return node.value def put(self, key: int, value: int) -> None:
# 新增缓存
if key not in self.cache:
node = DLinkList(key, value)
self.cache[key] = node
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
removed_node = self.removeTail()
del self.cache[removed_node.key]
self.size -= 1
else:
# 更新缓存
node = self.cache[key]
node.value = value
self.moveToHead(node) def removeTail(self):
# 移除尾部节点
node = self.tail.pre
self.removeNode(node)
# 这里仍旧需要将删除的节点返回,为了方便cache字典删除键值对
return node def removeNode(self, node):
# 移除某个节点
node.next.pre = node.pre
node.pre.next = node.next def moveToHead(self, node):
# 节点前移首部
self.removeNode(node)
self.addToHead(node) def addToHead(self, node):
# 添加到首部
node.next = self.head.next
node.pre = self.head
self.head.next.pre = node
self.head.next = node

注:

  • 字典的定义的键是查找值,键对应的值是双向链表对应节点的引用。
  • 双线链表的节点保存的键值对,好处在于,淘汰尾部节点的时候可以直接从节点取出键,进而删除字典中的键值对。
  • 查找数据的时候,如果缓存未命中,可以采用回调函数去查找数据库真实数据。如果命中,则返回数据的同时,仍需要将该数据对应的节点调整到链表首部,更新最近最少使用状态。
  • 添加数据的时候,如果缓存容量满了,则需要淘汰链表尾部节点,也就是最近最少访问的节点。

相关链接:leetcode:lru缓存

LRU缓存及实现的更多相关文章

  1. LRU缓存实现(Java)

    LRU Cache的LinkedHashMap实现 LRU Cache的链表+HashMap实现 LinkedHashMap的FIFO实现 调用示例 LRU是Least Recently Used 的 ...

  2. 转: LRU缓存介绍与实现 (Java)

    引子: 我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本, ...

  3. volley三种基本请求图片的方式与Lru的基本使用:正常的加载+含有Lru缓存的加载+Volley控件networkImageview的使用

    首先做出全局的请求队列 package com.qg.lizhanqi.myvolleydemo; import android.app.Application; import com.android ...

  4. 如何用LinkedHashMap实现LRU缓存算法

    阿里巴巴笔试考到了LRU,一激动忘了怎么回事了..准备不充分啊.. 缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的.LRU这个算法就是把最近一次 ...

  5. 面试挂在了 LRU 缓存算法设计上

    好吧,有人可能觉得我标题党了,但我想告诉你们的是,前阵子面试确实挂在了 RLU 缓存算法的设计上了.当时做题的时候,自己想的太多了,感觉设计一个 LRU(Least recently used) 缓存 ...

  6. Java集合详解5:深入理解LinkedHashMap和LRU缓存

    今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存. 摘要: HashMap和双向链表合二为一即是LinkedHashMap.所谓Linke ...

  7. 04 | 链表(上):如何实现LRU缓存淘汰算法?

    今天我们来聊聊“链表(Linked list)”这个数据结构.学习链表有什么用呢?为了回答这个问题,我们先来讨论一个经典的链表应用场景,那就是+LRU+缓存淘汰算法. 缓存是一种提高数据读取性能的技术 ...

  8. LRU缓存原理

    LRU(Least Recently Used)  LRU是近期最少使用的算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象. 采用LRU算法的缓存有两种:LrhCache和DisL ...

  9. 链表(上):如何实现LRU缓存淘汰算法?

    一.什么是链表 和数组一样,链表也是一种线性表. 从内存结构来看,链表的内存结构是不连续的内存空间,是将一组零散的内存块串联起来,从而进行数据存储的数据结构. 链表中的每一个内存块被称为节点Node. ...

  10. [Leetcode]146.LRU缓存机制

    Leetcode难题,题目为: 运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制.它应该支持以下操作: 获取数据 get 和 写入数据 put . 获取数据 get(key ...

随机推荐

  1. Eureka原理与架构

    一.原理图 Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址 提供者:启动后向Eureka注册自己信息(地址,提供什么服务) 消费者:向Eureka订阅服务,Eureka会将对应服 ...

  2. 运行flutter-填坑之旅

    运行flutter; 1.有一个问题,解决了好长时间 如果你安装了IntelliJ IDEA 2017.2.7 x64,运行flutter的时候报错,是因为IntelliJ IDEA 2017.2.7 ...

  3. linux简单命令汇总

    ls [选项] [文件或目录] -a 显示所有文件,包括隐藏文件 -l 显示详细信息 -d 查看目录属性 -h 人性化显示文件大小 -i 显示inode mkdir [选项] 目录名 -p 递归创建 ...

  4. 经典面试题:分布式缓存热点KEY问题如何解决--有赞方案

    有赞透明多级缓存解决方案(TMC) 一.引子 1-1. TMC 是什么 TMC ,即"透明多级缓存( Transparent Multilevel Cache )",是有赞 Paa ...

  5. http头文件

    http 文件头详解 HTTP(HyperTextTransferProtocol)是超文本传输协议的缩写, 它用于传送WWW方式的数据,关于HTTP协议的详细内容请参考RFC2616.HTTP协议采 ...

  6. 在 Dapr 中使用 Cron 绑定的计划任务

    我昨天写了一篇关于在微服务应用程序中采用Dapr的好处的文章<从服务之间的调用来看 我们为什么需要Dapr>[1], 在那篇文章中,我们专注于"服务调用"构建块 [2] ...

  7. .NET6: 开发基于WPF的摩登三维工业软件

    MS Office和VisualStudio一直引领着桌面应用的时尚潮流,大型的工业软件一般都会紧跟潮流,搭配着Ribbon和DockPanel风格的界面.本文将介绍WPF下两个轻量级的Ribbon和 ...

  8. java下载网络大文件之内存不够的解决办法(包含分片上传分片下载)

    一.背景 2020年11月份的时候,我做过一个项目,涉及到网络文件,比如第三方接口提供一个文件的下载地址,使用java去下载,当时我全部加在到JVM内存里面,话说,单单是80M的下载单线程没问题,但是 ...

  9. AT2272 [ARC066B] Xor Sum

    我们可以知道异或可以看成不进位的加法,那么我们就可以得到 \(a + b = a\) ^ \(b + ((a \& b) << 1)\),不难发现 \(\frac{v - u}{2 ...

  10. Android 关于Intent的一些简略总结

    感谢大佬:https://www.jianshu.com/p/19147a69e970 Intent 常用构造方法: | 方法 | 描述 | |Intent() | 构造一个空 Intent | | ...