Lua采用了自动内存管理机制。一个程序只需创建对象,而无需删除对象。通过使用垃圾
收集机制,Lua会自动地删除那些已成为垃圾的对象。这减轻了程序员在内存管理方面的
负担,更重要的是将程序员从许多内存的bug中(如无效指针、内存泄露)中解放出来。
垃圾收集器只能回收那些它认为是垃圾的东西,它不会回收那些用户认为是垃圾的东西。
如递减顶部索引后,之前的栈顶元素仍保留在数组中;存储在全局变量中的对象。这两种
情况需要用户手动将这些对象变量赋值为nil,才能释放它们。
弱引用table机制——用户能用它来告诉Lua一个引用不应该阻碍一个对象的回收,这样垃圾
收集器就会忽视这个对象的引用,从而达到回收这个对象的目的。
Lua用弱引用table来实现弱引用,一个弱引用table就是一个具有弱引用条目的table。
如果一个对象只被一个弱引用table所持有,那么Lua就会回收这个对象。
table中的key和value,都可以包含任意类型的对象,它们都是强引用,它们会阻止对
其所引用对象的回收。一个弱引用table中,key和value是可以回收的。有3种弱引用的
table: 具有弱引用的key的table、具有弱引用value的table、同时具有两种弱引用的
table。不论是哪种类型的弱引用table,只要一个key或value被回收了,那么它所在的
整个条目都会从table中删除。
一个table中的弱引用类型是通过元表中的__mode字段来决定的。这个字段的值应为一个
字符串,如果这个字符串中包含字母k,那么这个table的key是弱引用的;如果包含字母
v,那么这个table的value是弱引用的。如示例:
a = {}
b = {__mod = "k"}
setmetatable(a, b) --现在'a'的key就是弱引用
key = {} -- 创建第一个key
a[key] = 1
key = {} -- 创建第二个key
a[key] = 2
collectgarbage() --强制进行一次垃圾收集
for k, v in pairs(a) do print(v) end --> 2
本例中,第二句赋值key={}会覆盖第一个key。当收集器运行时,由于没有其它地方在引用
第一个key,因此第一个key就被回收了,并且table中相应的条目也被删除了。而第二个key,
变量key仍引用着它,因此没有被回收。
注意: Lua只会回收弱引用table中的对象。而像数字、布尔、字符串这样的值在作为key时是
不可回收的。除非它们关联的value被回收了,那么整个条目都会从这个弱引用table中删除。
17.1 备忘录(memoize)函数
备忘录函数是一项通用的编程技术,用空间来换取时间,来提高函数或程序的运行速度。
示例一:
每当服务器收到一个请求,它就要对代码字符串调用loadstring,然后再调用编译好的
函数。但loadstring是一个昂贵的函数,而有些发给服务器的命令具有很高的频率,如
"closeconnection()"。这时可以让服务器用一个辅助的table记录下所用调用loadstring
的结果,在每次调用loadstring前,服务器先检查这个table中是否已记录了代码字符串
编译后的结果。如果没有再调用loadstring,并将结果存储到table中。
这项优化节省的时间非常可观。但也会导致不易察觉的开销。如有些命令会重复出现,
有些命令只发生一次。而辅助table中会保存服务器收到的所有命令及其编译结果。
经过一段时间的累积后就会耗费服务器的内存。而弱引用的table正好可以解决这个问题。
local results = {}
setmetatable(results, {__mode="v"}) -- 使value成为弱引用
function mem_loadstring(s)
local res =results[s]
if res == nilthen
res = assert(loadstring(s))
results[s]= res
end
return res
end
示例二:
通过备忘录技术,确保某类对象的唯一性,以复用具有相同颜色的table。
local results = {}
setmetatable(results, {__mode = "v"}) -- 使value成为弱引用
function createRGB(r, g, b)
local key = r.."_".. g .."_".. b
local color =results[key]
if color ==nil then
color ={red=r, green=g, blue=b}
results[key]= color
end
return color
end
17.2 对象属性
关于弱引用table的另一项重要应用是将属性与对象关联起来。当然,使用外部table来关联
它们是一种理想的做法。可以将对象作为这个外部table的key,对象的属性作为这个外部table的value。
这个外部table可以保存任意对象的属性,当然Lua也允许将任何对象作为table的key。
但是这种做法有一个重大的缺陷。当用户将一个对象作为外部table的key时,就是引用了它。
Lua是无法回收一个作为table key的对象。这时用户就可以使用弱引用table来解决这个问题。
当然本例需要的只是弱引用key。当一个弱引用key没有其它引用时,Lua就会回收它。
17.3 回顾table的默认值
13.4.3节讨论了如何实现具有非nil默认值的table。其中注明了还有两种技术需要弱引用
table的支持。这里将介绍两种用于默认值的技术,它们其实是上述备忘录和对象属性的特殊应用。
第一种做法是使用一个弱引用table,通过它将每个table与其默认值关联起来:
local defaults = {}
setmetatable(defaults, {__mode = "k"})
local mt = {__index = function(t)
return defaults[t] end}
function setDefault(t, d)
defaults[t] =d
setmetatable(t, mt)
end
注--如果defaults没有弱引用key,它就会使所有具有默认值得table持久存在下去。
第二种做法是对每种不同的默认值使用不同的元表。不过,只要有重复的默认值,就
复用同样的元表。这是备忘录的典型应用:
local metas = {}
setmetatable(metas, {__mode = "v"})
function setDefault(t, d)
local mt =metas[d]
if mt == nilthen
mt ={__index = function() return d end}
meata[d]= mt
end
setmetatable(t,mt)
end
注--这里用到弱引用value,这样当metas中的元表在不使用时就可以被回收了。
这两种实现具有类似的复杂度和性能表现。第一种做法需要为每个table的默认值(defaults中的一个条目)
使用内存。而第二种做法则需要为每种不同的默认值使用一组内存(一个新table,一个新closure和metas中
的一个条目)。当程序中有上千个table和一些默认值时,第二种做法是首选;但如果只有很少的table共享
几个公用的默认值时,那么就应该选择第一种做法。