一、淘汰策略

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

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. Go语言系列之并发编程

    Go语言中的并发编程 并发与并行 并发:同一时间段内执行多个任务(宏观上并行,微观上并发). 并行:同一时刻执行多个任务(宏观和微观都是并行). Go语言的并发通过goroutine实现.gorout ...

  2. 新增访客数量MR统计之Reduce和Runner相关准备

    关注公众号:分享电脑学习回复"百度云盘" 可以免费获取所有学习文档的代码(不定期更新)云盘目录说明:tools目录是安装包res 目录是每一个课件对应的代码和资源等doc 目录是一 ...

  3. Hive的基本概念和常用命令

    原文链接: https://www.toutiao.com/i6766571623727235595/?group_id=6766571623727235595 一.概念: 1.结构化和非结构化数据 ...

  4. iview 按需引入解决加载慢的问题

    如果出现加载2s以上的情况请先查看服务器是否对大文件进行过压缩优化处理. 按照官方文档把iview引入到vue的项目中,全部引入的时候没问题.当按官方文档显示的按需加载是借助插件babel-plugi ...

  5. Android官方文档翻译 七 2.Adding the Action Bar

    Adding the Action Bar 增加一个Action Bar(工具栏) The action bar is one of the most important design element ...

  6. idea 插件推荐

    工欲善其事必先利其器,本文介绍几个自己在开发过程中常用的idea插件 安装方法 idea 里面在线安装 settings>plugins>marketplace 里面搜索安装 idea 官 ...

  7. 【测试数据】android下CPU核与线程数的关系

    测试方法 24MB的一张4K图片,连续计算5次直方图. 小米mix2s, 高通骁龙 845.4大核,4小核. 数据表格 线程数 绝对时间(s) 累计CPU时间(s) 每线程平均耗时(us) 每线程最大 ...

  8. 【摘抄】疑问chatterbot

    ChatterBot使用哪种机器学习? -------------------------------------------------- -  简而言之,ChatterBot使用了许多不同的机器学 ...

  9. Redis的过期删除策略(和内存淘汰机制)-转

    版权声明:本文为CSDN博主「奥修诺斯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/qq_39944869/ ...

  10. vector自实现(一)

    vector.h: #ifndef __Vector__H__ #define __Vector__H__ typedef int Rank; #define DEFAULT_CAPACITY 3 t ...