前置知识: Lua

弱表

00:00
2 min Advanced 2026/6/14

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

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式