首页 / 技术文章地图 / 正文

【Lua进阶】弱表 weak table:缓存与观察者的正确姿势

发布:2026-09-20 07:58 | 作者:996 技术组 | 2 阅读
完整课程入口:996 全套课程体系Lua 学习路径幂尔框架 mirs.cn

强引用是默认,弱引用要声明

Lua 的 GC 判断"对象是否可回收"看的是引用:只要还有 table 拿着一个对象,它就不会被回收。这带来一个常见死结——你想给一堆对象挂附加数据(缓存计算结果、临时标记),用普通 table 存 cache[obj] = result,结果 obj 被别处销毁了,cache 还死死抓着它,内存永远降不下来。

弱表就是解药:把表的 __mode 元字段设为 "k"(key 弱)、"v"(value 弱)或 "kv",对应位置的引用不计入 GC 判定。对象一旦在其他地方都不可达,它在弱表里的条目会被自动清除。

lua
local cache = setmetatable({}, { __mode = "v" })  -- value 弱引用

cache[player] = computeExpensive(player)   -- 玩家对象销毁后
-- cache[player] 条目自动消失,无需手动清理

三个标准应用场景

第一,计算缓存。 给对象附加派生数据(战力值、格式化名称):用弱 value 表缓存,对象活着缓存有效,对象没了缓存自动蒸发,零维护成本。注意 key 用对象本身、value 放结果,而不是反过来。

第二,观察者与事件回调池。 事件总线若用强引用存监听者,忘记反注册就泄漏。把监听者表做成 __mode = "v" 的弱值表,销毁的对象自动从监听列表里蒸发,等于免费的自动反注册(前提是监听者没有其他强引用,用匿名闭包时要留全局句柄)。

第三,对象附加属性袋。 想给引擎传进来的 userdata/外部队象"贴标签"又不能改它的结构:用弱 key 表 attach[obj] = {...},生命周期与对象严格同生共死。

必须知道的行为细节

弱表的清理时机由 GC 决定:对象不可达后,条目要等下一轮回收才消失,期间访问可能仍拿到旧值——别把弱表当成"销毁通知器",销毁回调请走显式接口。其次,__mode = "k" 时数字 key 不会被弱化;值类型的数字、布尔、短字符串本身不受弱引用影响(它们不可被回收)。最后,弱表遍历中做增删可能拿到"将死"条目,遍历逻辑要对 nil 值保持宽容。

一个经验法则:给"别人的对象"挂数据用弱 key 表,给自己算的结果做缓存用弱 value 表。记住这句,弱表就不会用歪。

观测工具

release_print(msg)

打印消息到控制台,看弱表里元素是否被回收,最直接的办法是打长度

参数类型说明
msgany打印内容

printusetime(play, on, off)

打印脚本总耗时(微秒),弱表作为缓存时,读放大是否可接受要实测

参数类型说明
playobject玩家对象
oninteger1=开始技术2=结束计时并打印耗时
offinteger2=结束计时

变量清理

clearhumcustvar(actor, var)

清理个人自定义变量,缓存的真正持有方在引擎侧时,必须显式清理

参数类型说明
actorobject要清理的人物对象传入 * 表示清理所有玩家(必填参数)
varstring变量名,* -所有变量(必填参数)

clearglobalcustvar(var)

清理自定义系统变量,全局缓存的清理口,弱表管不到引擎持有的引用

参数类型说明
varstring变量名, * =所有变量(必填参数)

日志

logact(play, logAct, loginfo, nParam1, nParam2, nParam3, nParam4, nParam5)

自定义日志,观察缓存命中与失效用日志比 print 更可控

参数类型说明
playobject玩家对象
logActinteger日志ID大于等于10000以上
loginfostring日志内容
nParam1integer整数型(可空)最大支持21亿
nParam2integer整数型(可空)最大支持21亿
nParam3integer整数型(可空)最大支持21亿
作者履历与出处
本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理,讲解体系出自多年商业端开发生产一线。作者团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。
© 威海旷世互娱 · 返回文章地图 · 课程体系 · 幂尔框架