再谈LRU双链表内存管理
N年前我写了个双链表也发了博客,还添了代码。但是那个代码不但复杂,而且还有有问题的,一直懒得整理,放在空间误导别人。最近在写服务端,今天抽点空补一篇。
关于LRU网上随便搜,有过后端经验的人应该很多都研究过。所谓双链表一个是哈希表,用于通过Key来查数据,另一个表是用来表示顺序,越前面的元素越新(也可以理解为越接近当前系统时间)。我以前写那个LRU,用一个哈希和一个数组,查哈希没什么问题,但是查数组用了indexof和splice就问题大了,呵呵,每次get数据都splice一次,那效率烂shi了。
正确的做法只需要一个哈希数组就可以了,另一个链表并不需要另开数组存,只需要给入库的哈希对象包一个新对象,新对象有prev和next即上一个下一个两节点即可表示先后顺序。另外再需要top和bottom两个变量来存头尾。
用一行代码表达:map[key] = {target=target,key=key,prev=XX,next=XX}。
最近在写lua,贴一段lua版本的lru
-- 双链表LRU内存管理
-- 充分利用空间来换时间查找和删除过期数据
-- 哈希cacheMap用于主键存取查找,另一个链表是每个节点的prev和next来表示时间先后
-- Author: Pelephone
-- Date:2016-04-16 16:53:36 LRUMgr = class(LRUMgr) -- 初始
function LRUMgr:__init()
-- 过期时间(多少秒之后过期)
self.expireTime = ** -- 顶部节点,最新访问
self.top = nil
-- 最后节点,最旧的元素
self.bottom = nil -- 过期时间(多少秒之后过期)
self.expireTime = **
-- 最大缓存个数
self.maxLen = -- 目标对象的映射
self.cacheMap = {}
setmetatable(self.cacheMap,{__mode = "k"}) -- 总共缓存的数量
self.totLen =
end -- 添加一个缓存对象
function LRUMgr:set(key,target)
local cacheObj = self.cacheMap[key]
if not cacheObj then
cacheObj = {key=key,target=target}
self.cacheMap[key] = cacheObj if not self.top and not self.bottom then
self.top = cacheObj
self.bottom = cacheObj
end
self.totLen = self.totLen +
end -- get一下放直队顶
self:get(key) -- 超过最大缓存量,移出一下队尾
if self.totLen > self.maxLen then
self:remove(self.bottom.key)
end
end -- 获取缓存,返回对象的同时把对象移动队顶
function LRUMgr:get(key)
local cacheObj = self.cacheMap[key]
if not cacheObj then
return nil
end if cacheObj == self.top then
cacheObj.time = self:getNowTime()
return cacheObj.target
end -- 上下节点连接,然后把当前节放到队顶
if cacheObj.prev and cacheObj.next then
local tmpNext = cacheObj.prev
cacheObj.prev.next = cacheObj.next
cacheObj.next.prev = tmpNext
end -- 新对象插入队头,队头是最新命中的节点
if self.top then
self.top.prev = cacheObj
end
cacheObj.next = self.top
cacheObj.prev = nil
self.top = cacheObj
cacheObj.time = self:getNowTime()
return cacheObj.target
end -- 移出缓存
function LRUMgr:remove(key)
local cacheObj = self.cacheMap[key]
if not cacheObj then
return nil
end -- 上下节点连接,然后把当前节放到队顶
if cacheObj == self.top then
self.top = self.top.next
if self.top then
self.top.prev = nil
end
if self.totLen == then
self.bottom = nil
end
elseif cacheObj == self.bottom then
self.bottom = self.bottom.prev
if self.bottom then
self.bottom.next = nil
end
if self.totLen == then
self.top = nil
end
else
local tmpNext = cacheObj.prev
cacheObj.prev.next = cacheObj.next
cacheObj.next.prev = tmpNext
end
self.totLen = self.totLen -
self.cacheMap[key] = nil
cacheObj.prev = nil
cacheObj.next = nil
cacheObj.target = nil
end -- 清理过期对象
function LRUMgr:clearExpire()
local nExpireTime = self:getNowTime() - self.expireTime
-- 从队尾开始删除缓存,直到删到没到期的对象
while self.totLen > and self.bottom.time < nExpireTime do
local newBtm = self.bottom.prev
if newBtm then
newBtm.next = nil
end self.cacheMap[self.bottom.key] = nil
self.bottom.prev = nil
self.bottom.next = nil
self.bottom.target = nil self.totLen = self.totLen -
self.bottom = newBtm
end
end -- 清除所有缓存
function LRUMgr:removeALl()
-- for k,v in pairs(self.cacheMap) do
-- self.cacheMap[k] = nil
-- end
self.cacheMap = {}
setmetatable(self.cacheMap,{__mode = "k"})
self.top = nil
self.bottom = nil
end -- 获取当前时间点
function LRUMgr:getNowTime()
return os.time()
end -- 获取缓存长度
function LRUMgr:getLength()
return self.totLen
end -- 创建一次数组返回(此方法有性能问题,甚用,仅用于查看顺序)
function LRUMgr:getList()
if self.totLen == then
return {}
end local ls = {}
local cacheObj = self.top
table.insert(ls,cacheObj.target)
while cacheObj.next ~= nil do
table.insert(ls,cacheObj.next.target)
cacheObj = cacheObj.next
end
return ls
end
lua lru
对象池的话也可以在这个的基础上封装,代码就懒得粘了。
除了双链外我以前还搞过一种时间块三链的存储结构,性能效率也不错,不过算法有些复杂,也不知道是不是我独创,总之网是搜不到。思路是把缓存分时间块存取,例如十分钟内的缓存在第一块,十到二十分钟的缓存在第二块,类堆。每次访问缓存就把缓存对象放到最新的时间块,过期处理是把过期时间块里所有缓存对象清了,例如五十到六十分钟时间块过期了,就把时间块置空即可,时间块LRU的好处是十分钟内的缓存被访问是不需要进行上下节点处理的,而且清内存的时候不需要对多个对象进行置空清除,只需要对时间块清除即可。
具体做法是取当前时间戳除以一个时间段数值(例如十分钟是60*10),取整数部份做为时间块的id,用这个id做为这个时间段的内存块加入链表头。每调用对象就把对象放到放到最新的时间块去。这个方法不是判断对象过期,而是判断时间块过期。时间块过期就把块id对应的对象置空。懒筋抽搐,改天有空再弄上来。
再谈LRU双链表内存管理的更多相关文章
- 【Linux】浅谈段页式内存管理
让我们来回顾一下历史,在早期的计算机中,程序是直接运行在物理内存上的.换句话说,就是程序在运行的过程中访问的都是物理地址.如果这个系统只运行一个程序,那么只要这个程序所需的内存不要超过该机器的物理内存 ...
- 浅谈JavaScript中的内存管理
一门语言的内存存储方式是我们学习他必须要了解的,接下来让我浅谈一下自己对他的认识. 首先说,JavaScript中的变量包含两种两种类型: 1)值类型或基本类型:undefined.null.numb ...
- linux内存管理
一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分: 1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...
- 关于linux内存管理
Linux的内存管理主要分为两部分:物理地址到虚拟地址的映射,内核内存分配管理(主要基于slab). 物理地址到虚拟地址之间的映射 1.概念 物理地址(physical address) 用于内存芯 ...
- 《objective-c基础教程》学习笔记(十)—— 内存管理
本篇博文,将给大家介绍下再Objective-C中如何使用内存管理.一个程序运行的时候,如果不及时的释放没有用的空间内存.那么,程序会越来越臃肿,内存占用量会不断升高.我们在使用的时候,就会感觉很卡, ...
- Windows内存管理[转]
本文主要内容:1.基本概念:物理内存.虚拟内存:物理地址.虚拟地址.逻辑地址:页目录,页表2.Windows内存管理3.CPU段式内存管理4.CPU页式内存管理 一.基本概念1. 两个内存概念物理内存 ...
- Linux内存管理--虚拟地址、逻辑地址、线性地址和物理地址的区别(二)【转】
本文转载自:http://blog.csdn.net/yusiguyuan/article/details/9668363 这篇文章中介绍了四个名词的概念,下面针对四个地址的转换进行分析 CPU将一个 ...
- linux内存管理---物理地址、线性地址、虚拟地址、逻辑地址之间的转换
linux内存管理---虚拟地址.逻辑地址.线性地址.物理地址的区别(一) 这篇文章中介绍了四个名词的概念,下面针对四个地址的转换进行分析 CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步 ...
- linux内存管理---虚拟地址、逻辑地址、线性地址、物理地址的区别(一)
分析linux内存管理机制,离不了上述几个概念,在介绍上述几个概念之前,先从<深入理解linux内核>这本书中摘抄几段关于上述名词的解释: 一.<深入理解linux内核>的解释 ...
随机推荐
- [Java Web] 5、JSP (1) 注释 & Scriptlet
>_<" 在JSP中支持两种注释的语法操作,一种是显式注释,这种注释客户端是允许看见的,另外一种是隐式注释,此种注释客户端是无法看见的. 显式注释语法: <!-- 注释内容 ...
- [MFC] 如何更改MFC程序图标
方法一: >_<:找一张ICO图标,替换programname/res/programname.ico文件,就可以啦,这时候你运行后得到的图标可能还是原来MFC的默认图标,这时候你只要把工 ...
- eclipse运行maven的jetty插件内存溢出
系统运行在Maven中的Jetty插件下,当在Eclipse运行clean jetty:run时,系统提示OutOfMemoryError: PermGen space.解决办法:设置run as - ...
- hibernate 一对一关联关系 及其懒加载,总结
一对一,则有主对象和从对象之区别 若无主对象,则从对象亦无存在之意义. 主对象为person 从对象为idcard <hibernate-mapping package="cn.itc ...
- [数据库操作]Java中的JDBC的使用方法.
前言:想必大家在实际编码中都遇到过JDBC的操作, 这里仅做自己的一个总结, 有错误和不完整之处还请大家提出来. 1,JDBC其实一套规范(接口)数据库厂商需要实现此接口(实现类)--数据库驱动 2, ...
- Python之Django【基础篇】
Python的WEB框架有Django.Tornado.Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了ORM.模型绑定.模板引擎.缓存.Session等诸多功能. ...
- Python错误、调试和测试
try: print('try...') r = 10 / int('a') print('result:', r) except ValueError as e: print('ValueError ...
- ios之VFL的补充(二)
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[languageI ...
- Docker实践(5)—资源隔离
Docker使用cgroup实现CPU,内存和磁盘IO等系统资源的限制. CPU Docker现在有2个与CPU资源相关的参数,-c可以指定CPU的占比,--cpuset可以绑定CPU.例如,指定容器 ...
- 推荐算法——距离算法
本文内容 用户评分表 曼哈顿(Manhattan)距离 欧式(Euclidean)距离 余弦相似度(cos simliarity) 推荐算法以及数据挖掘算法,计算"距离"是必须的~ ...