lua-lru 是 Lua 语言中的一个 LRU(Least Recently Used,最近最少使用)缓存实现,是一个基于哈希表和双向链表的数据结构,用于管理缓存中的数据项,并根据访问频率自动淘汰最不常用的数据项
-- lua-lru, LRU cache in Lua
-- Copyright (c) 2015 Boris Nagaev
-- See the LICENSE file for terms of use.
local lru = {}
function lru.new(max_size, max_bytes)
assert(max_size >= 1, "max_size must be >= 1")
assert(not max_bytes or max_bytes >= 1,
"max_bytes must be >= 1")
-- current size
local size = 0
local bytes_used = 0
-- map is a hash map from keys to tuples
-- tuple: value, prev, next, key
-- prev and next are pointers to tuples
local map = {}
-- indices of tuple
local VALUE = 1
local PREV = 2
local NEXT = 3
local KEY = 4
local BYTES = 5
-- newest and oldest are ends of double-linked list
local newest = nil -- first
local oldest = nil -- last
local removed_tuple -- created in del(), removed in set()
-- remove a tuple from linked list
local function cut(tuple)
local tuple_prev = tuple[PREV]
local tuple_next = tuple[NEXT]
tuple[PREV] = nil
tuple[NEXT] = nil
if tuple_prev and tuple_next then
tuple_prev[NEXT] = tuple_next
tuple_next[PREV] = tuple_prev
elseif tuple_prev then
-- tuple is the oldest element
tuple_prev[NEXT] = nil
oldest = tuple_prev
elseif tuple_next then
-- tuple is the newest element
tuple_next[PREV] = nil
newest = tuple_next
else
-- tuple is the only element
newest = nil
oldest = nil
end
end
-- insert a tuple to the newest end
local function setNewest(tuple)
if not newest then
newest = tuple
oldest = tuple
else
tuple[NEXT] = newest
newest[PREV] = tuple
newest = tuple
end
end
local function del(key, tuple)
map[key] = nil
cut(tuple)
size = size - 1
bytes_used = bytes_used - (tuple[BYTES] or 0)
removed_tuple = tuple
end
-- removes elemenets to provide enough memory
-- returns last removed element or nil
local function makeFreeSpace(bytes)
while size + 1 > max_size or
(max_bytes and bytes_used + bytes > max_bytes)
do
assert(oldest, "not enough storage for cache")
del(oldest[KEY], oldest)
end
end
local function get(_, key)
local tuple = map[key]
if not tuple then
return nil
end
cut(tuple)
setNewest(tuple)
return tuple[VALUE]
end
local function set(_, key, value, bytes)
local tuple = map[key]
if tuple then
del(key, tuple)
end
if value ~= nil then
-- the value is not removed
bytes = max_bytes and (bytes or #value) or 0
makeFreeSpace(bytes)
local tuple1 = removed_tuple or {}
map[key] = tuple1
tuple1[VALUE] = value
tuple1[KEY] = key
tuple1[BYTES] = max_bytes and bytes
size = size + 1
bytes_used = bytes_used + bytes
setNewest(tuple1)
else
assert(key ~= nil, "Key may not be nil")
end
removed_tuple = nil
end
local function delete(_, key)
return set(_, key, nil)
end
local function mynext(_, prev_key)
local tuple
if prev_key then
tuple = map[prev_key][NEXT]
else
tuple = newest
end
if tuple then
return tuple[KEY], tuple[VALUE]
else
return nil
end
end
-- returns iterator for keys and values
local function lru_pairs()
return mynext, nil, nil
end
local mt = {
__index = {
get = get,
set = set,
delete = delete,
pairs = lru_pairs,
},
__pairs = lru_pairs,
}
return setmetatable({}, mt)
end
return lru
主要功能和作用
1. 缓存管理:
- lua-lru 可创建一个最多可以存储 max_size 个数据项的缓存,并且可以选择限制缓存的总字节数 max_bytes
- 当缓存达到最大容量时,会自动淘汰最久未使用的数据项,以腾出空间给新数据
2. 数据存储与访问:
- set(key, value, bytes):将数据项存储到缓存中,如果缓存中已经存在相同的 key,则会替换旧数据,如果缓存达到最大容量或字节限制,会自动淘汰最久未使用的数据项
- get(key):从缓存中获取数据项,如果数据项存在,将其移动到链表的最新位置(即标记为最近使用)
- delete(key):从缓存中删除指定的数据项
3. 迭代器:
- lru_pairs():返回一个迭代器,用于遍历缓存中的所有数据项,迭代器按最近使用到最久未使用的顺序遍历
4. 容量管理:
- makeFreeSpace(bytes):当缓存即将超出容量限制时,自动删除最久未使用的数据项,直到有足够的空间或字节数
具体实现细节
- 双向链表:用于维护数据项的访问顺序,最近访问的数据项放在链表的前端,最久未访问的数据项放在链表的后端
- 哈希表:用于快速查找数据项,哈希表的键是缓存的键,值是一个包含数据、链表前后指针等信息的数据结构
- 自动淘汰:当缓存达到最大容量或字节数限制时,通过删除链表尾部的数据项来释放空间
使用场景
- 数据缓存:适用于需要缓存数据的场景,特别是在数据访问频率高但存储空间有限的情况下
- 限流:可以用于实现限流机制,淘汰掉长时间未使用的数据项,确保缓存中保留的是最近活跃的数据
- 性能优化:在一些需要频繁访问数据但数据量较大的场景中,通过 LRU 缓存可以显著提高访问性能
使用示例
local lru = require("lru")
local cache = lru.new(3, 100) -- 创建一个最多存储3个数据项,最大100字节的缓存
cache:set("key1", "value1", 10) -- 存储数据项,占用10字节
cache:set("key2", "value2", 20) -- 存储数据项,占用20字节
cache:set("key3", "value3", 30) -- 存储数据项,占用30字节
print(cache:get("key1")) -- 输出 "value1"
cache:set("key4", "value4", 40) -- 存储新数据项,占用40字节,淘汰 "key1" 因为它是最近最少使用的
print(cache:get("key1")) -- 输出 nil,因为 "key1" 已经被淘汰
for key, value in cache:pairs() do
print(key, value) -- 按最近使用到最久未使用的顺序遍历缓存
end