• 再谈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对应的对象置空。懒筋抽搐,改天有空再弄上来。

  • 相关阅读:
    poj(1458)(最长公共子序列)
    二叉搜索树
    hdu1087
    poj3641(学习了)
    平年和闰年的由来。。。。
    Linux system函数返回值(转)
    VS2010单元测试(转)
    QT QTableWidget 用法总结(转)
    QT显示图片(转)
    Qt正则表达式类QRegExp(转)
  • 原文地址:https://www.cnblogs.com/pelephone/p/lru_lua.html
Copyright © 2020-2023  润新知