lua程序设计第4版 23.垃圾收集

23.1 弱引用表       

        当一个对象没有任何地方引用它的时候,垃圾收集器会把这个对象视为垃圾,回收这个对象。但是当我们这些对象原本被保存在一个表里,除了这个用作保存的表,别的地方再无引用的时候。垃圾收集器也不会回收这个垃圾对象,原因就是这个垃圾对象还被这个表引用,导致无法回收。这时候就需要弱引用表。

         弱引用表就是告知lua语言一个引用不应该阻止对一个对象回收的机制。lua语言中,也只通过弱引用表来实现弱引用。

         一个表是否为弱引用表是由其元表中的__mode字段来决定的,其值为一个字符串。当值为"k"时,代表这个表中的键是弱引用的;为"v"时,代表值是弱引用的;为"kv"时代表键和值都是弱引用的。下面放个例子

a={}
mt={__mode="k"}
setmetatable(a,mt)    --现在表a的键是弱引用的了
key={}
a[key]=1
key={}     --这时候第一个键key的值就被覆盖了
a[key]=2
collectgarbage()  --强制进行垃圾收集
for k,v in pairs(a) do
    print(v)
end
------>   2    --第一个键原本由字段key所引用,但是第二个键覆盖了key的值,所以第一个键现在只被这个表a所引用,而表a的键又是弱引用,所以当我们进行垃圾回收,就回收了第一个键值

 注意:只有对象可以从弱引用表中被移除,而数字和bool这样的值是没有办法被回收的。假如在上面这个表a里插入一个值类型的键,是永远无法被回收的。只有在一个值为弱引用的表中,当值被回收之后,整个元素都会从表中删除,字符串也一样

 

23.2 记忆函数(Memorize Function)

        空间换时间是一种常见的编程技巧,我们可以通过记忆函数的执行结果,后续使用相同参数再次调用该函数时直接返回之前记忆(其实就是保存)的结果,来加快函数的运行速度。

         假设有一个通用的服务器,这个服务器它接受的请求是以字符串形式保存的lua代码,每收到一个请求,它都会对字符串运行load函数,然后再调用编译后的函数。而load函数的开销很昂贵,而发送给服务器的某些命令出现频率还会很高。这时候就让服务器用一个表来记忆所有的函数load的执行结果,这样每次在调用函数load前线检查一下表里是否记忆了已经处理过的结果,如下:

local results={}
function mem_loadstring(s)
    local res=results[s]
    if res == nil then         --检查是否已有结果
        res==assert(load(s))   --计算新结果
        results[s]=res         --保存结果以便后续使用
    end
    return res
end

        这样一来,它可以节省的开销非常可观,但是也有一个不易察觉的资源浪费问题。虽然有很多命令会重复出现,但也有不少的命令就出现一次,渐渐地,表results中会堆积服务器上收到的所有命令和编译结果,长时间后,会耗尽服务器上的内存。

        那么弱引用表就解决了这个这个问题,如果表中的值是弱引用的,那么每个垃圾周期都会删除所有那个时刻未使用的编译结果(基本上就是全部):

local results={}
setmetatable(results,{__mode="v"})
...

         事实上,这里也可以直接把键和值都设置为弱引用的,因为键都是字符串, 最终的效果是完全一样的。

setmetatable(results,{__mode="kv"})

 

23.3 对象属性

         这一章作者讲了弱引用表的另外一个重要作用,是将属性与对象关联起来。我写了下面一个例子:

t={}                                  --应该存放属性的原始表
relevance={}                          --关联表,将原始表中的每个属性存放在不同的大表中
mt={__newindex = function(t,k,v)      --用newindex进行关联
	local propertytable=relevance.k   
	if propertytable == nil then
		relevance.k={}
		relevance.k[t]=v
	else
		propertytable[t]=v
	end
end
}
setmetatable(t,mt)

        上面的例子是用对偶配合__newindex实现的一个属性表,把原始表t中的每个属性都定义在外部,比如t.a最终会存为a[t],而这些属性a,b,c等等则是一个单独的表,表中以对象本身来保存属性的值。这样一来存在外部的属性就不会干扰到其他对象,只有赋值了这个属性值的对象才拥有它。

         但是上面例子也有个缺陷,那就是因为我们把对象当作了属性表的键,那么这个对象就会因为有这个属性表的引用而永远无法被回收,这时候我们会把属性表设为有弱引用键的表,注意这里不能是弱引用的值,因为这个值是可能对应着多个对象,则会导致活跃对象的属性也被回收。

         所以,改动如下,为每个属性表设定弱引用键:

t={}                                  --应该存放属性的原始表
relevance={}                          --关联表,将原始表中的每个属性存放在不同的大表中
mt={__newindex = function(t,k,v)      --用newindex进行关联
	local propertytable=relevance.k   
	if propertytable == nil then
		relevance.k={}
        setmetatable(relevance.k,{__mode="k"})
		relevance.k[t]=v
	else
		propertytable[t]=v
	end
end
}
setmetatable(t,mt)

 

23.4 回顾具有默认值的表

       首先看一下具有默认值的表的实现方式:

local defaults={}
local mt={__index=function(t) return default[t] end}
function SetDefault(t,d)
{
    default[t]=d
    setmetatable(t,mt)
}

       这样一看,似乎不会有有问题,当某个表调用了setfault这个函数设置了默认值以后,每当访问不存在的字段,都会调用到元表中__index的function,以自身来访问defaults表,得到设置好的默认值d.

       但是这里就有了一个问题。也就是我们设置过默认值的这个表永远存在一个引用,那就是defaults表中。所以导致垃圾回收器无法回收它,它就会永远存在下去,所以这里我们将defaults表的键值设为弱引用,这样就可以告知lua语言不要因此引用而阻止到垃圾回收器的机制。

local defaults={}
setmetatable(defaults,{__mode="k"})
local mt={__index=function(t) return default[t] end}
function SetDefault(t,d)
{
    default[t]=d
    setmetatable(t,mt)
}

       以上也是对偶表示的一种典型的应用,实现了具有非nil默认值的表。

       第二种解决方案是一个记忆技术的典型应用。对不同默认值使用不同的元表,对具有相同默认值的表去复用元表:

local metas={}
setmetatable(metas,{__model="v"})
function setDefault(t,d)
    local mt=metas[d]
    if mt ==nil then
        mt ={__index=function() return d end}
        metas[d]=mt
    end
    setmetatable(t,mt)
end

       具体用哪种方式是取决于实际情况。第一钟方式为每个具有默认值的表分配了几个字节的内存,而第二种则是为每个不同默认值的表分配若干内存(一个新表,一个闭包和metas里的一个元素)。

 

23.5  瞬表(Ephemeron Table)

        看23.3中的例子,假如这时候我们设置了其中一个属性是返回当前对象的闭包。比如:

relevance.a={}
setmetatable(relevance.a,{__mode="k"})
relevance.a[t]=function () 
    return t
end

        这时候我们虽然把relevance中的表a设为了弱引用键的表,但是表中的值却不是弱引用的,所以对每个函数来说都存在一个强引用。而这个函数又指向其对应的对象,那这个对象也存在一个强引用,所以不会被回收。

        但是lua 5.2版本引入了瞬表,在lua中,拥有一个具有弱引用键和强引用值的表示瞬表。.在瞬表中,有这样一个概念,一个键的可访问性控制着对应值的可访问性,换句话说,指向v的引用只有当存在某些指向k的外部引用存在是才是强引用,否则即使v引用了k,也算是弱引用,k一样会被回收。

        像这里假如外部没有了对k的引用,那么只剩下了v对k的引用,而由于瞬表的机制v对k的引用也变成了弱引用,那么最终就会回收这个对象,这也是瞬表解决的循环引用的问题。

 

23.6 析构器(Finalizer)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值