弱表
00:00
Lua弱表详解:weak table自动回收。
概述
弱表(Weak Table)是 Lua 的内存管理特性,通过元表的 __mode 字段指定键或值为弱引用。当弱引用的对象不再被其他地方引用时,垃圾回收器会自动从弱表中移除该条目,避免内存泄漏。
基础概念
弱表模式
| 模式 | __mode 值 | 说明 |
|---|---|---|
| 键弱引用 | ”k” | 键为弱引用,值不受影响 |
| 值弱引用 | ”v” | 值为弱引用,键不受影响 |
| 全弱引用 | ”kv” | 键和值都为弱引用 |
弱引用的含义
- 强引用:阻止对象被垃圾回收
- 弱引用:不阻止对象被垃圾回收
- 当对象只剩下弱引用时,GC 会回收该对象并从弱表中移除对应条目
快速上手
创建弱表
-- 键弱引用表
local wk = setmetatable({}, { __mode = "k" })
-- 值弱引用表
local wv = setmetatable({}, { __mode = "v" })
-- 键和值都弱引用
local wkv = setmetatable({}, { __mode = "kv" })
基本行为
-- 值弱引用:值被回收后条目自动移除
local cache = setmetatable({}, { __mode = "v" })
local key = "user:1"
local data = { name = "Alice", scores = { 95, 87, 92 } }
cache[key] = data
print(cache[key]) -- table: 0x...
data = nil -- 移除强引用
collectgarbage() -- 触发 GC
print(cache[key]) -- nil(值已被回收,条目被移除)
详细用法
键弱引用
-- 键弱引用:适合以对象为键的映射
local objectMap = setmetatable({}, { __mode = "k" })
local obj = { id = 1, name = "test" }
objectMap[obj] = "对象元数据"
print(objectMap[obj]) -- "对象元数据"
obj = nil -- 移除对对象的强引用
collectgarbage()
-- objectMap 中以该对象为键的条目已被移除
值弱引用缓存
-- 使用值弱引用实现自动清理的缓存
local cache = setmetatable({}, { __mode = "v" })
function getCached(key, factory)
local value = cache[key]
if value then
print("缓存命中: " .. key)
return value
end
print("缓存未命中: " .. key)
value = factory()
cache[key] = value
return value
end
-- 使用
local data1 = getCached("config", function()
return { timeout = 30, retries = 3 }
end)
-- 第二次调用会命中缓存
local data2 = getCached("config", function()
return { timeout = 30, retries = 3 }
end)
对象属性表
-- 使用键弱引用为对象附加属性,不阻止对象回收
local objectProps = setmetatable({}, { __mode = "k" })
local obj1 = { name = "对象1" }
local obj2 = { name = "对象2" }
-- 为对象附加属性
objectProps[obj1] = { createdAt = os.time(), visible = true }
objectProps[obj2] = { createdAt = os.time(), visible = false }
-- 获取属性
print(objectProps[obj1].visible) -- true
-- 对象被回收后,属性自动清理
obj1 = nil
collectgarbage()
-- objectProps 中 obj1 的条目已被移除
常见场景
记忆化函数
-- 使用弱表实现记忆化,缓存自动清理
function memoize(fn)
local cache = setmetatable({}, { __mode = "v" })
return function(...)
local key = table.concat({...}, ",")
local result = cache[key]
if result then return unpack(result) end
result = { fn(...) }
cache[key] = result
return unpack(result)
end
end
-- 使用
local slowFib = memoize(function(n)
if n < 2 then return n end
return slowFib(n - 1) + slowFib(n - 2)
end)
print(slowFib(30)) -- 第一次计算
print(slowFib(30)) -- 第二次命中缓存
观察者模式
-- 使用弱引用存储观察者,避免阻止观察者被回收
local Observable = {}
Observable.__index = Observable
function Observable.new()
return setmetatable({
observers = setmetatable({}, { __mode = "v" })
}, Observable)
end
function Observable:subscribe(observer)
table.insert(self.observers, observer)
end
function Observable:notify(event)
for _, observer in ipairs(self.observers) do
if observer.onEvent then observer:onEvent(event) end
end
end
-- 使用
local subject = Observable.new()
local listener = { onEvent = function(self, e) print("收到: " .. e) end }
subject:subscribe(listener)
subject:notify("更新")
注意事项
- 字符串和数字类型的键/值不会被弱引用回收,因为它们不是垃圾回收对象
- 只有 table 类型的对象才能被弱引用回收
- 弱表的 __mode 只能在创建时设置,之后不能修改
- collectgarbage() 可以手动触发 GC 来验证弱表行为
- 数值和布尔值作为键时,弱引用不生效
- 弱表条目的移除发生在 GC 周期中,不是立即的
进阶用法
弱引用与终结器配合
-- 使用弱引用 + 终结器实现资源清理
local resources = setmetatable({}, { __mode = "v" })
function createResource(name)
local resource = { name = name, handle = openHandle(name) }
-- 设置终结器
local proxy = newproxy(true)
local mt = getmetatable(proxy)
mt.__gc = function()
closeHandle(resource.handle)
print("资源已释放: " .. name)
end
resources[name] = resource
return resource, proxy
end
双向映射
-- 使用两个弱表实现双向映射
local idToObj = setmetatable({}, { __mode = "v" })
local objToId = setmetatable({}, { __mode = "k" })
local nextId = 1
function register(obj)
local id = nextId
nextId = nextId + 1
idToObj[id] = obj
objToId[obj] = id
return id
end
function getById(id)
return idToObj[id]
end
function getId(obj)
return objToId[obj]
end