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双链表内存管理的更多相关文章

  1. 【Linux】浅谈段页式内存管理

    让我们来回顾一下历史,在早期的计算机中,程序是直接运行在物理内存上的.换句话说,就是程序在运行的过程中访问的都是物理地址.如果这个系统只运行一个程序,那么只要这个程序所需的内存不要超过该机器的物理内存 ...

  2. 浅谈JavaScript中的内存管理

    一门语言的内存存储方式是我们学习他必须要了解的,接下来让我浅谈一下自己对他的认识. 首先说,JavaScript中的变量包含两种两种类型: 1)值类型或基本类型:undefined.null.numb ...

  3. linux内存管理

    一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:    1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...

  4. 关于linux内存管理

     Linux的内存管理主要分为两部分:物理地址到虚拟地址的映射,内核内存分配管理(主要基于slab). 物理地址到虚拟地址之间的映射 1.概念 物理地址(physical address) 用于内存芯 ...

  5. 《objective-c基础教程》学习笔记(十)—— 内存管理

    本篇博文,将给大家介绍下再Objective-C中如何使用内存管理.一个程序运行的时候,如果不及时的释放没有用的空间内存.那么,程序会越来越臃肿,内存占用量会不断升高.我们在使用的时候,就会感觉很卡, ...

  6. Windows内存管理[转]

    本文主要内容:1.基本概念:物理内存.虚拟内存:物理地址.虚拟地址.逻辑地址:页目录,页表2.Windows内存管理3.CPU段式内存管理4.CPU页式内存管理 一.基本概念1. 两个内存概念物理内存 ...

  7. Linux内存管理--虚拟地址、逻辑地址、线性地址和物理地址的区别(二)【转】

    本文转载自:http://blog.csdn.net/yusiguyuan/article/details/9668363 这篇文章中介绍了四个名词的概念,下面针对四个地址的转换进行分析 CPU将一个 ...

  8. linux内存管理---物理地址、线性地址、虚拟地址、逻辑地址之间的转换

    linux内存管理---虚拟地址.逻辑地址.线性地址.物理地址的区别(一) 这篇文章中介绍了四个名词的概念,下面针对四个地址的转换进行分析 CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步 ...

  9. linux内存管理---虚拟地址、逻辑地址、线性地址、物理地址的区别(一)

    分析linux内存管理机制,离不了上述几个概念,在介绍上述几个概念之前,先从<深入理解linux内核>这本书中摘抄几段关于上述名词的解释: 一.<深入理解linux内核>的解释 ...

随机推荐

  1. [WinAPI] API 12 [获取程序所在的目录、程序模块路径,获取和设置当前目录]

    Windows系统提供一组API实现对程序运行时相关目录的获取和设置.用户可以使用GetCurrentDirectory和SetCurrentDirectory获取程序的当前目录,获取模块的路径使用G ...

  2. C#与数据库访问技术总结(六)之Command对象创建SQl语句代码示例

    Command对象创建SQl语句代码示例 说明:前面介绍了 Command 对象的方法和一些属性,回顾一下 Command对象主要用来执行SQL语句.利用Command对象,可以查询数据和修改数据. ...

  3. Nodejs学习笔记(十)--- 与MongoDB的交互(mongodb/node-mongodb-native)、MongoDB入门

    目录 简介 MongoDB安装(windows) MongoDB基本语法和操作入门(mongo.exe客户端操作) 库操作 插入 查询 修改 删除 存储过程 nodejs操作MongoDB 插入 查询 ...

  4. 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 ...

  5. 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. 可插性   ...

  6. javascript基础知识复习一

    JavaScript 一.数据类型 A.String B.Number C.boolean  1.undefined.false.null.0.“”这五个返回的都是false: 2.NAN==NAN返 ...

  7. MS SQL SERVER索引优化相关查询

        查找缺失索引 -- =============================================   -- Description: 查询当前数据库中缺失的索引,知道你进行优化的 ...

  8. JAVA “Run as administrator” “UAC disabled” alternative solution

    Technorati 标签: psexec,run as administrator,UAC java.io.IOException: Cannot run program "psexec. ...

  9. 简单好用的sshfs -- 通过ssh映射远程路径(转)

    最近习惯性访问N个Linux机器,在不同机器间跳来跳去,很是麻烦,最终,找到了sshfs,可以把远程目录直接映射到本地,无需修改远程机器的设置,仅要求有ssh连接的权限(ssh都没有的话,还能干啥?! ...

  10. 使用 dbms_xplan.display 按照 plan_hash_value 查执行计划的方法

    dbms_xplan.display_* 能按照 plan_hash_value 只有 display_awr 方法,如果这个SQL PLAN 刚刚生成,没有写入到AWR怎么办呢? 可以将 V$SQL ...