再谈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

  1 -- 双链表LRU内存管理
  2 -- 充分利用空间来换时间查找和删除过期数据
  3 -- 哈希cacheMap用于主键存取查找,另一个链表是每个节点的prev和next来表示时间先后
  4 -- Author: Pelephone
  5 -- Date:2016-04-16 16:53:36
  6 
  7 LRUMgr = class(LRUMgr)
  8 
  9 -- 初始
 10 function LRUMgr:__init()
 11     -- 过期时间(多少秒之后过期)
 12     self.expireTime = 60*60*2
 13 
 14     -- 顶部节点,最新访问
 15     self.top = nil
 16     -- 最后节点,最旧的元素
 17     self.bottom = nil
 18 
 19     -- 过期时间(多少秒之后过期)
 20     self.expireTime = 60*60*2
 21     -- 最大缓存个数
 22     self.maxLen = 9999
 23 
 24     -- 目标对象的映射
 25     self.cacheMap = {}
 26      setmetatable(self.cacheMap,{__mode = "k"})
 27 
 28     -- 总共缓存的数量
 29     self.totLen = 0
 30 end
 31 
 32 -- 添加一个缓存对象
 33 function LRUMgr:set(key,target)
 34     local cacheObj = self.cacheMap[key]
 35     if not cacheObj then
 36         cacheObj = {key=key,target=target}
 37         self.cacheMap[key] = cacheObj
 38 
 39         if not self.top and not self.bottom then
 40             self.top = cacheObj
 41             self.bottom = cacheObj
 42         end
 43         self.totLen = self.totLen + 1
 44     end
 45 
 46     -- get一下放直队顶
 47     self:get(key)
 48 
 49     -- 超过最大缓存量,移出一下队尾
 50     if self.totLen > self.maxLen then
 51         self:remove(self.bottom.key)
 52     end
 53 end
 54 
 55 -- 获取缓存,返回对象的同时把对象移动队顶
 56 function LRUMgr:get(key)
 57     local cacheObj = self.cacheMap[key]
 58     if not cacheObj then
 59         return nil
 60     end
 61 
 62     if cacheObj == self.top then
 63         cacheObj.time = self:getNowTime()
 64         return cacheObj.target
 65     end
 66 
 67     -- 上下节点连接,然后把当前节放到队顶
 68     if cacheObj.prev and cacheObj.next then
 69         local tmpNext = cacheObj.prev
 70         cacheObj.prev.next = cacheObj.next
 71         cacheObj.next.prev = tmpNext
 72     end
 73 
 74     -- 新对象插入队头,队头是最新命中的节点
 75     if self.top then
 76         self.top.prev = cacheObj
 77     end
 78     cacheObj.next = self.top
 79     cacheObj.prev = nil
 80     self.top = cacheObj
 81     cacheObj.time = self:getNowTime()
 82     return cacheObj.target
 83 end
 84 
 85 -- 移出缓存
 86 function LRUMgr:remove(key)
 87     local cacheObj = self.cacheMap[key]
 88     if not cacheObj then
 89         return nil
 90     end
 91 
 92     -- 上下节点连接,然后把当前节放到队顶
 93     if cacheObj == self.top then
 94         self.top = self.top.next
 95         if self.top then
 96             self.top.prev = nil
 97         end
 98         if self.totLen == 1 then
 99             self.bottom = nil
100         end
101     elseif cacheObj == self.bottom then
102         self.bottom = self.bottom.prev
103         if self.bottom then
104             self.bottom.next = nil
105         end
106         if self.totLen == 1 then
107             self.top = nil
108         end
109     else
110         local tmpNext = cacheObj.prev
111         cacheObj.prev.next = cacheObj.next
112         cacheObj.next.prev = tmpNext
113     end
114     self.totLen = self.totLen - 1
115     self.cacheMap[key] = nil
116     cacheObj.prev = nil
117     cacheObj.next = nil
118     cacheObj.target = nil
119 end
120 
121 -- 清理过期对象
122 function LRUMgr:clearExpire()
123     local nExpireTime = self:getNowTime() - self.expireTime
124     -- 从队尾开始删除缓存,直到删到没到期的对象
125     while self.totLen > 0 and self.bottom.time < nExpireTime do
126         local newBtm = self.bottom.prev
127         if newBtm then
128             newBtm.next = nil
129         end
130 
131         self.cacheMap[self.bottom.key] = nil
132         self.bottom.prev = nil
133         self.bottom.next = nil
134         self.bottom.target = nil
135 
136         self.totLen = self.totLen - 1
137         self.bottom = newBtm
138     end
139 end
140 
141 -- 清除所有缓存
142 function LRUMgr:removeALl()
143     -- for k,v in pairs(self.cacheMap) do
144     --     self.cacheMap[k] = nil
145     -- end
146     self.cacheMap = {}
147      setmetatable(self.cacheMap,{__mode = "k"})
148      self.top = nil
149      self.bottom = nil
150 end
151 
152 -- 获取当前时间点
153 function LRUMgr:getNowTime()
154     return os.time()
155 end
156 
157 -- 获取缓存长度
158 function LRUMgr:getLength()
159     return self.totLen
160 end
161 
162 -- 创建一次数组返回(此方法有性能问题,甚用,仅用于查看顺序)
163 function LRUMgr:getList()
164     if self.totLen == 0 then
165         return {}
166     end
167 
168     local ls = {}
169     local cacheObj = self.top
170     table.insert(ls,cacheObj.target)
171     while cacheObj.next ~= nil do
172         table.insert(ls,cacheObj.next.target)
173         cacheObj = cacheObj.next
174     end
175     return ls
176 end
lua lru

对象池的话也可以在这个的基础上封装,代码就懒得粘了。 

除了双链外我以前还搞过一种时间块三链的存储结构,性能效率也不错,不过算法有些复杂,也不知道是不是我独创,总之网是搜不到。思路是把缓存分时间块存取,例如十分钟内的缓存在第一块,十到二十分钟的缓存在第二块,类堆。每次访问缓存就把缓存对象放到最新的时间块,过期处理是把过期时间块里所有缓存对象清了,例如五十到六十分钟时间块过期了,就把时间块置空即可,时间块LRU的好处是十分钟内的缓存被访问是不需要进行上下节点处理的,而且清内存的时候不需要对多个对象进行置空清除,只需要对时间块清除即可。

具体做法是取当前时间戳除以一个时间段数值(例如十分钟是60*10),取整数部份做为时间块的id,用这个id做为这个时间段的内存块加入链表头。每调用对象就把对象放到放到最新的时间块去。这个方法不是判断对象过期,而是判断时间块过期。时间块过期就把块id对应的对象置空。懒筋抽搐,改天有空再弄上来。

原文地址:https://www.cnblogs.com/pelephone/p/lru_lua.html