完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
Lua 的 GC 判断"对象是否可回收"看的是引用:只要还有 table 拿着一个对象,它就不会被回收。这带来一个常见死结——你想给一堆对象挂附加数据(缓存计算结果、临时标记),用普通 table 存 cache[obj] = result,结果 obj 被别处销毁了,cache 还死死抓着它,内存永远降不下来。
弱表就是解药:把表的 __mode 元字段设为 "k"(key 弱)、"v"(value 弱)或 "kv",对应位置的引用不计入 GC 判定。对象一旦在其他地方都不可达,它在弱表里的条目会被自动清除。
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)
打印消息到控制台,看弱表里元素是否被回收,最直接的办法是打长度
| 参数 | 类型 | 说明 |
|---|---|---|
| msg | any | 打印内容 |
printusetime(play, on, off)
打印脚本总耗时(微秒),弱表作为缓存时,读放大是否可接受要实测
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| on | integer | 1=开始技术2=结束计时并打印耗时 |
| off | integer | 2=结束计时 |
clearhumcustvar(actor, var)
清理个人自定义变量,缓存的真正持有方在引擎侧时,必须显式清理
| 参数 | 类型 | 说明 |
|---|---|---|
| actor | object | 要清理的人物对象传入 * 表示清理所有玩家(必填参数) |
| var | string | 变量名,* -所有变量(必填参数) |
clearglobalcustvar(var)
清理自定义系统变量,全局缓存的清理口,弱表管不到引擎持有的引用
| 参数 | 类型 | 说明 |
|---|---|---|
| var | string | 变量名, * =所有变量(必填参数) |
logact(play, logAct, loginfo, nParam1, nParam2, nParam3, nParam4, nParam5)
自定义日志,观察缓存命中与失效用日志比 print 更可控
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| logAct | integer | 日志ID大于等于10000以上 |
| loginfo | string | 日志内容 |
| nParam1 | integer | 整数型(可空)最大支持21亿 |
| nParam2 | integer | 整数型(可空)最大支持21亿 |
| nParam3 | integer | 整数型(可空)最大支持21亿 |