LRUCache的实现原理及利用python实现的方法


Posted in Python onNovember 21, 2017

简介

LRU(Least Recently Used)最近最少使用,最近有时间和空间最近的歧义,所以我更喜欢叫它近期最少使用算法。它的核心思想是,如果一个数据被访问过,我们有理由相信它在将来被访问的概率就越高。于是当LRU缓存达到设定的最大值时将缓存中近期最少使用的对象移除。LRUCache内部使用LinkedHashMap来存储key-value键值对,并将LinkedHashMap设置为访问顺序来体现LRU算法。

无论是对某个key的get,还是set都算做是对该key的一次使用。当set一个不存在的key,并且LRU Cache中key的数量超过cache size的时候,需要将使用时间距离现在最长的那个key从LRU Cache中清除。

LRU Cache实现

在Java中,LRUCache是通过LinkedHashMap实现的。鄙人照猫画虎,实现一个Python版的LRU Cache(可能和其他大神的实现有所区别)。

首先,需要说明的是:

LRU Cache对象内部会维护一个 双端循环链表 的 头节点

LRU Cache对象内部会维护一个dict

内部dict的value都是Entry对象,每个Entry对象包含:

  • key的hash_code(hash_code = hash(key),在本实现中,hash_code相同的不同key,会被当作一个key来处理。因此,对于自定义类,应该实现魔术方法:__hash__)
  • v - (key, value)对中的value
  • prev - 前一个对象
  • next - 后一个对象

具体实现是:

当从LRU Cache中get一个key的时候:

  • 计算该key的hash_code
  • 从内部dict中获取到entry
  • 将该entry移动到 双端循环链表 的 第一个位置
  • 返回entry.value

当向LRU Cache中set一个(key, value)对的时候:

计算该key的hash_code,

从LRU Cache的内部dict中,取出该hash_code对应的old_entry(可能不存在),然后根据(key, value)对生成一个new_entry,之后执行:

  • dict[hash_code] = new_entry
  • 将new_entry提到 双端循环链表 的第一个位置
  • 如果old_entry存在,则从链表中删除old_entry
  • 如果是新增了一个(key, value)对,并且cache中key的数量超过了cache size,那么将双端链表的最后一个元素删除(该元素就是那个最近最少被使用的元素),并且从内部dict中删除该元素

HashMap的实现原理

(面试过程中也经常会被问到):数组和链表组合成的链表散列结构,通过hash算法,尽量将数组中的数据分布均匀,如果hashcode相同再比较equals方法,如果equals方法返回false,那么就将数据以链表的形式存储在数组的对应位置,并将之前在该位置的数据往链表的后面移动,并记录一个next属性,来指示后移的那个数据。

注意:数组中保存的是entry(其中保存的是键值)

Python实现

class Entry:
 def __init__(self, hash_code, v, prev=None, next=None):
 self.hash_code = hash_code
 self.v = v
 self.prev = prev
 self.next = next

 def __str__(self):
 return "Entry{hash_code=%d, v=%s}" % (
  self.hash_code, self.v)
 __repr__ = __str__

class LRUCache:
 def __init__(self, max_size):
 self._max_size = max_size
 self._dict = dict()
 self._head = Entry(None, None)
 self._head.prev = self._head
 self._head.next = self._head

 def __setitem__(self, k, v):
 try:
  hash_code = hash(k)
 except TypeError:
  raise

 old_entry = self._dict.get(hash_code)
 new_entry = Entry(hash_code, v)
 self._dict[hash_code] = new_entry

 if old_entry:
  prev = old_entry.prev
  next = old_entry.next
  prev.next = next
  next.prev = prev

 head = self._head
 head_prev = self._head.prev
 head_next = self._head.next

 head.next = new_entry
 if head_prev is head:
  head.prev = new_entry
 head_next.prev = new_entry
 new_entry.prev = head
 new_entry.next = head_next

 if not old_entry and len(self._dict) > self._max_size:
  last_one = head.prev
  last_one.prev.next = head
  head.prev = last_one.prev
  self._dict.pop(last_one.hash_code)

 def __getitem__(self, k):
 entry = self._dict[hash(k)]
 head = self._head
 head_next = head.next
 prev = entry.prev
 next = entry.next

 if entry.prev is not head:
  if head.prev is entry:
  head.prev = prev
  head.next = entry

  head_next.prev = entry
  entry.prev = head
  entry.next = head_next

  prev.next = next
  next.prev = prev

 return entry.v

 def get_dict(self):
 return self._dict

if __name__ == "__main__":
 cache = LRUCache(2)
 inner_dict = cache.get_dict()

 cache[1] = 1
 assert inner_dict.keys() == [1], "test 1"
 cache[2] = 2
 assert sorted(inner_dict.keys()) == [1, 2], "test 2"
 cache[3] = 3
 assert sorted(inner_dict.keys()) == [2, 3], "test 3"
 cache[2]
 assert sorted(inner_dict.keys()) == [2, 3], "test 4"
 assert inner_dict[hash(2)].next.v == 3
 cache[4] = 4
 assert sorted(inner_dict.keys()) == [2, 4], "test 5"
 assert inner_dict[hash(4)].v == 4, "test 6"

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
用Python设计一个经典小游戏
May 15 Python
Python基于list的append和pop方法实现堆栈与队列功能示例
Jul 24 Python
Python3.6 Schedule模块定时任务(实例讲解)
Nov 09 Python
python脚本监控Tomcat服务器的方法
Jul 06 Python
ubuntu16.04制作vim和python3的开发环境
Sep 23 Python
Python操作SQLite数据库过程解析
Sep 02 Python
Python字符串、列表、元组、字典、集合的补充实例详解
Dec 20 Python
python pandas利用fillna方法实现部分自动填充功能
Mar 16 Python
Tensorflow中的降维函数tf.reduce_*使用总结
Apr 20 Python
Python selenium模拟手动操作实现无人值守刷积分功能
May 13 Python
python 删除excel表格重复行,数据预处理操作
Jul 06 Python
python爬虫请求库httpx和parsel解析库的使用测评
May 10 Python
Python利用itchat对微信中好友数据实现简单分析的方法
Nov 21 #Python
python中is与双等于号“==”的区别示例详解
Nov 21 #Python
Python使用PIL模块生成随机验证码
Nov 21 #Python
Python3中条件控制、循环与函数的简易教程
Nov 21 #Python
Python3 循环语句(for、while、break、range等)
Nov 20 #Python
Python虚拟环境项目实例
Nov 20 #Python
Python插件virtualenv搭建虚拟环境
Nov 20 #Python
You might like
测试php函数的方法
2013/11/13 PHP
腾讯微博提示missing parameter errorcode 102 错误的解决方法
2014/12/22 PHP
Joomla语言翻译类Jtext用法分析
2016/05/05 PHP
PHP使用 Pear 进行安装和卸载包的方法详解
2019/07/08 PHP
从javascript语言本身谈项目实战
2006/12/27 Javascript
Jquery中Ajax 缓存带来的影响的解决方法
2011/05/19 Javascript
如何防止回车(enter)键提交表单
2014/05/11 Javascript
jquery常用方法及使用示例汇总
2014/11/08 Javascript
node.js使用npm 安装插件时提示install Error: ENOENT报错的解决方法
2014/11/20 Javascript
javascript制作的cookie封装及使用指南
2015/01/02 Javascript
nodejs实现获取某宝商品分类
2015/05/28 NodeJs
js中的面向对象入门
2017/03/06 Javascript
layui中layer前端组件实现图片显示功能的方法分析
2017/10/13 Javascript
vue实现带过渡效果的下拉菜单功能
2020/02/19 Javascript
快速了解Vue父子组件传值以及父调子方法、子调父方法
2020/07/15 Javascript
vue element-ul实现展开和收起功能的实例代码
2020/11/25 Vue.js
[02:38]2018年度DOTA2最佳劣单位选手-完美盛典
2018/12/17 DOTA
Google开源的Python格式化工具YAPF的安装和使用教程
2016/05/31 Python
python求最大连续子数组的和
2018/07/07 Python
解决python通过cx_Oracle模块连接Oracle乱码的问题
2018/10/18 Python
Python装饰器基础概念与用法详解
2018/12/22 Python
手机使用python操作图片文件(pydroid3)过程详解
2019/09/25 Python
python代码实现将列表中重复元素之间的内容全部滤除
2020/05/22 Python
python切割图片的示例
2020/11/12 Python
最新pycharm安装教程
2020/11/18 Python
Grid 宫格常用布局的实现
2020/01/10 HTML / CSS
美味咖啡的顶级烘焙师:Cafe Britt
2018/03/15 全球购物
美国家居装饰和豪华家具购物网站:One Kings Lane
2018/12/24 全球购物
Proenza Schouler官方网站:纽约女装和配饰品牌
2019/01/03 全球购物
军训感想500字
2014/02/20 职场文书
建筑工程质量通病防治方案
2014/06/08 职场文书
五心教育心得体会
2014/09/04 职场文书
幼儿园小班见习报告
2014/10/31 职场文书
新生开学寄语大全
2015/05/28 职场文书
vue-cropper插件实现图片截取上传组件封装
2021/05/27 Vue.js
HTTP中的Content-type详解
2022/01/18 HTML / CSS