再谈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内核>的解释 ...
随机推荐
- [WinAPI] API 12 [获取程序所在的目录、程序模块路径,获取和设置当前目录]
Windows系统提供一组API实现对程序运行时相关目录的获取和设置.用户可以使用GetCurrentDirectory和SetCurrentDirectory获取程序的当前目录,获取模块的路径使用G ...
- C#与数据库访问技术总结(六)之Command对象创建SQl语句代码示例
Command对象创建SQl语句代码示例 说明:前面介绍了 Command 对象的方法和一些属性,回顾一下 Command对象主要用来执行SQL语句.利用Command对象,可以查询数据和修改数据. ...
- Nodejs学习笔记(十)--- 与MongoDB的交互(mongodb/node-mongodb-native)、MongoDB入门
目录 简介 MongoDB安装(windows) MongoDB基本语法和操作入门(mongo.exe客户端操作) 库操作 插入 查询 修改 删除 存储过程 nodejs操作MongoDB 插入 查询 ...
- Atiti.ui原理与gui理论
Atiti.ui原理与gui理论 1. 概论2 2. ui的类型2 2.1. RMGUI vs IMGUI2 2.2. Cli2 2.3. Gui2 2.4. Nui natural user int ...
- atitit.j2ee 1.5 1.6 的不同跟 Servlet 3.0新特性总结
atitit.j2ee 1.5 1.6 的不同跟 Servlet 3.0新特性总结 1. jar比较,j2ee 1.6 添加了许多的jar 1 2. ,Servlet 3.0 2 2.1. 可插性 ...
- javascript基础知识复习一
JavaScript 一.数据类型 A.String B.Number C.boolean 1.undefined.false.null.0.“”这五个返回的都是false: 2.NAN==NAN返 ...
- MS SQL SERVER索引优化相关查询
查找缺失索引 -- ============================================= -- Description: 查询当前数据库中缺失的索引,知道你进行优化的 ...
- JAVA “Run as administrator” “UAC disabled” alternative solution
Technorati 标签: psexec,run as administrator,UAC java.io.IOException: Cannot run program "psexec. ...
- 简单好用的sshfs -- 通过ssh映射远程路径(转)
最近习惯性访问N个Linux机器,在不同机器间跳来跳去,很是麻烦,最终,找到了sshfs,可以把远程目录直接映射到本地,无需修改远程机器的设置,仅要求有ssh连接的权限(ssh都没有的话,还能干啥?! ...
- 使用 dbms_xplan.display 按照 plan_hash_value 查执行计划的方法
dbms_xplan.display_* 能按照 plan_hash_value 只有 display_awr 方法,如果这个SQL PLAN 刚刚生成,没有写入到AWR怎么办呢? 可以将 V$SQL ...