Lua 基础之弱引用 table

来源:互联网 发布:濮阳市公务员网络 编辑:程序博客网 时间:2024/05/16 11:56

弱引用 table

  • lua 的垃圾回收器只会回收没有引用的对象,有些时候并不能回收程序员认为的垃圾。比如数组里的元素在其它地方已经没有引用了,但因为还在数组中,因此垃圾回收器并不会去回收它
  • 弱引用 table 告诉回收器一个元素在 table 中的引用不应该阻止它的回收。如果一个对象的引用都是弱引用,那回收器就会回收这个对象
  • 弱引用 table 有三种:弱引用 key,弱引用 value 和弱引用 key-value。设置 table 的元表的 __mode 元字段就可以设置一个表为弱引用表,__mode = “k” 表示是弱引用 key 表,__mode = “v” 表示是弱引用 value 表,__mode = “kv” 表示是弱引用 key-value 表
  • lua 只会回收弱引用 table 中的对象,对于数字、布尔、字符串此类的值是不会回收的
table = {}setmetatable(table, {__mode = "kv"})key = {}table[key] = 10key = {}table[key] = 20table.x = {}table.y = "hello"for k, v in pairs(table) do    print(k, v)endprint("----------")-- 强制垃圾回收collectgarbage()for k, v in pairs(table) do    print(k, v)end

看一下输出结果

table: 005FC1A8 20table: 005FC158 10x   table: 005FC180y   hello----------table: 005FC1A8 20y   hello

原来的 table 共有 4 个数据,强制垃圾回收后只剩下两个。第二个 key 定义的时候会覆盖掉第一个,因此第一个 key 所指的对象就没有引用了;另外 x 和 y 的值也没有引用,但 y 的值是字符串,其 key-value 类型都是值类型,因此不会被回收

备忘录 memoize

备忘录是一种以空间换时间的机制,调用一个函数时,将某些参数的计算结果保存到一个备忘录 table 中,下次调用函数传进来同样的参数时,直接从备忘录 table 中取值。这种方式有个问题就是很容易产生垃圾数据,某些结果可能只使用一次,后面不会再用到了,但这些结果被备忘录 table 引用着,因此不会被回收。解决的方法就是使用弱引用 table。

local color = {}setmetatable(color, {__mode = "v"})create_color = function(r, g, b)    local key = r .. "-" .. g .. "-" .. b    local res = color[key]    if res == nil then        res = {red = r, green = g, blue = b}        color[key] = res    end    return resendlocal color1 = create_color(255, 255, 255)local color2 = create_color(255, 255, 255)print(color1 == color2) -- true

对象属性

有时候需要将属性绑定到一个对象,如果是对象是 table 和话可以定义几个 key 来保存这些属性,但如果是其它对象或者不想打乱 table 的结构的话就必须另外找方法了。比如一个函数要绑定一个名字属性,一个数组要绑定一个大小属性,一个表要绑定一个默认值属性。有一种解决方案就是使用一个外部 table 来保存对象和属性之间的对应关系,但这样的话这些对象就被多引用了一次,可能导致永远无法回收,这时很自然地就想到使用弱引用 table。这里使用弱引用 key,即当对象没有引用时回收,而不是当属性没引用时回收

local property = {}local mt = {__mode = "k"}setmetatable(property, mt)local array = {1, 2, 3, 4, 5}-- 保存对象属性property[array] = {size = 10}for i = 1, property[array].size do    print(i, array[i])endarray = nilcollectgarbage()print(#property) -- 0

回顾 table 的默认值

在介绍元表的时候我们已经讲过可以使用 table 的 __index 元方法来设置 table 的默认值,前面介绍了两种方式,这里再介绍两种

使用弱引用 table 保存每个 table 和它的默认值

local defaults = {}setmetatable(defaults, {__mode = "k"})local mt = {__index = function(t)        return defaults[t]    end}set_default = function(t, d)    defaults[t] = d    setmetatable(t, mt)endlocal t = {}set_default(t, 100)print(t.x)

这种方式把表的默认值保存到一个外部 table 中,然后所有的 table 共享一个元表,这个元表的 __index 元方法从外部 table 中获取默认值。把外部 table 设计成弱引用 table,这样垃圾回收器才能正常回收 table 对象

把 table 的元表设计成备忘录

local metas = {}setmetatable(metas, {__mode = "k"})local set_default = function(t, d)    local mt = metas[d]    if mt == nil then        mt = {__index = function()                return d            end}        metas[d] = mt    end    setmetatable(t, mt)endlocal t = {}set_default(t, 666)local tt = {}set_default(tt, 666)print(t.x, tt.y)

这种方式会给每个 table 设计一个新的元表,然后把默认值-元表保存到外部 table 中,下次如果一个新的 table 的默认值在外部 table 中可以找到,则使用之前定义的元表而不新创建元表

总结

到目前为止设置 table 的默认值共有四种方式,共同的思想是使用元表的 __index 元方法来返回默认值,不同的点有是独立元表还是共享元表,默认值存放在哪里
1. 每个 table 独立一个 metatable,由 __index 元方法直接返回默认值
2. 所有 table 共享一个 metatable,默认值存放在每个 table 本身,__index 元方法从 table 中取默认值返回
3. 所有 table 共享一个 metatable,所有默认值存放在一个外部 table 中,__index 元方法从外部 table 中取默认值返回
4. 一个默认值对应一个 metatable,存放在外部 table 中,使用备忘录的方式给每个 table 设置元表
* 最简单的方式是第一种,但这种方式需要的时间和空间都比较大
* 第二种跟第三种很相似,都是共享一个元表,然后保存默认值;这种方式省去了频繁创建元表的时间,空间上也较第一种有改进
* 第四种虽然也是为每个表创建一个元表,但相同的默认值使用了备忘录的机制,空间和时间都比第一种要好

选择: 如果 table 很多且有很多重复的默认值,则使用第四种方式;如果只有个别的 table 或者默认值很少重复,则使用第三种方式