再谈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内核>的解释 ...
随机推荐
- Nginx学习笔记(三) Nginx基本数据结构
Nginx基本数据结构 话说学习一种编程语言,例如C语言,我们首先学的也是数据结构,这是以后开发程序的关键.为了更好更方便的开发Nginx,Nginx自己实现了很多适合nginx的数据结构. Ngin ...
- [ACM_数学] LA 3708 Graveyard [墓地雕塑 圈上新加点 找规律]
Description Programming contests became so popular in the year 2397 that the governor of New Earck ...
- IBM的“认知计算时代”
IBM 提出信息技术进入“认知计算时代”.所有电子设备都有潜力发展出认知能力,换言之,都可以像人一样‘思考’. 何为认知计算时代呢? 认知计算系统能够学习并与人类自然地交流,以扩展人类或机器可亲自执 ...
- 基于iscroll.js实现下拉刷新和上拉加载特效
现在已经不是纯Android独霸天下的时代了,H5嵌入Android的Hybrid混合开发是大势所趋.今天给大家带来的就是移动端中常见的"上拉刷新,下拉加载"特效,这个特效将会基于 ...
- [读书笔记]C#学习笔记四: C#2.0泛型 可控类型 匿名方法和迭代器
前言 C#1.0的委托特性使方法作为其他方法的参数来传递,而C#2.0 中提出的泛型特性则使类型可以被参数化,从而不必再为不同的类型提供特殊版本的实现方法.另外C#2.0还提出了可空类型,匿名方法和迭 ...
- paip.提升性能---jvm java 工具使用.
paip.提升性能---jvm java 工具使用. 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn ...
- atitit.提升开发效率---使用服务器控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比较
atitit.提升开发效率---使用服务器控件生命周期 asp.net 11个阶段 java jsf 的6个阶段比较 如下列举了服务器控件生命周期所要经历的11个阶段. (1)初始化-- --在此 ...
- atitit.dw不能显示正确的百分比高度in dw的解决
atitit.dw不能显示正确的百分比高度in dw的解决 div 设置35%的高度,三,不能正确的显示高度...环境dw cs6 但是设置161px奏能ok了...表明这个是dw的一个bug... ...
- js高仿QQ消息列表左滑功能
该组件,主要功能类似于QQ消息列表左滑出现删除.标为已读等按钮的功能:现在的版本用的是纯javaScript编写:后续会跟进 angularJs 开发的类似组件以及jquery的; 下面,就让我们来认 ...
- 转:SDL2源代码分析
1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...