再谈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内核>的解释 ...
随机推荐
- unity3D——自带寻路Navmesh入门教程(二)(转)
转自:http://liweizhaolili.blog.163.com/blog/static/16230744201271210237616/ 上一节简单介绍了NavMesh寻路的基本用法,这次来 ...
- Hello Vagrant
回想以前,想要安装个虚拟机是多么的麻烦.先要费尽心机找到想要的操作系统镜像文件,然后安装虚拟化软件,按照其提供的GUI界面操作一步步创建,整个过程费时费力.但是,自从使用了Vagrant以后,咱腰不酸 ...
- Lucene
Lucene 是apache软件基金会一个开放源代码的全文检索引擎工具包,是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎. Lucene的目的是为软件开发人员提供一个简单易 ...
- Atitti.java exp ast java表达式语法ast构造器
Atitti.java exp ast java表达式语法ast构造器 /atiplat_cms/src/com/attilax/lang/AstParser.java 原理 分割tokens_sli ...
- paip.enhes efis 自动获取文件的中文编码
paip.enhes efis 自动获取文件的中文编码 ##为什么需要自动获取文件的中文编码 提高开发效率,自动获取文件的中文编码 .不需要手动设置编码...轻松的.. ##cpdetector 可 ...
- 更新日志 - fir.im 主题壁纸来了
fir.im 产品开发团队最近主要在优化应用管理后台和 BugHD 后台,新版应用管理后台很快会与大家见面. 本周其他更新内容简单概述如下: 1.fir.im 工具页添加壁纸主题包 有很多用户很喜欢 ...
- Leetcode 8 String to Integer (atoi) 字符串处理
题意:将字符串转化成数字. 前置有空格,同时有正负号,数字有可能会溢出,这里用long long解决(leetcode用的是g++编译器),这题还是很有难度的. class Solution { pu ...
- AutoMapper完成Dto与Model的转换
在实际的软件开发项目中,我们的“业务逻辑”常常需要我们对同样的数据进行各种变换. 例如,一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.相反,当用户请求数 ...
- ZZmsvcprt.lib(MSVCP90.dll) : error LNK2005:已经在libcpmtd.lib(xmutex.obj) 中定义 .的分析解决办法 (转)
很久没有写程式设计入门知识的相关文章了,这篇文章要来谈谈程式库 (Library) 连结,以及关于 MSVC 与 CRT 之间的种种恩怨情仇. 如果你使用的作业系统是 Linux.Mac 或其他非 W ...
- SSH使用教程( Bitvise Tunnelier+Chrome+Proxy Switchy)
前言 网上很多讲解使用Bitvise Tunnelier+Chrome+Proxy Switchy进行SSHFQ操作的教材有所缺失的部分,不太全面,这里重新整理. 本篇博客的主要内容如下: 准备工作 ...