Lua弱引用table及用弱引用table实现备忘录(弱引用相关)

Weak 表

Lua 自动进行内存的管理。程序只能创建对象(表,函数等),而没有执行删除对象的函数。通过使用垃圾收集技术,Lua 会自动删除那些失效的对象。这可以使你从内存管理的负担中解脱出来。更重要的,可以让你从那些由此引发的大部分 BUG 中解脱出来,比如指针挂起(dangling pointers)和内存溢出。

和其他的不同,Lua 的垃圾收集器不存在循环的问题。在使用循环性的数据结构的时候,你无须加入特殊的操作;他们会像其他数据一样被收集。当然,有些时候即使更智能化的收集器也需要你的帮助。没有任何的垃圾收集器可以让你忽略掉内存管理的所有问题。

垃圾收集器只能在确认对象失效之后才会进行收集;它是不会知道你对垃圾的定义的。一个典型的例子就是堆栈:有一个数组和指向栈顶的索引构成。你知道这个数组中有效的只是在顶端的那一部分,但 Lua 不那么认为。如果你通过简单的出栈操作提取一个数组元素,那么数组对象的其他部分对 Lua 来说仍然是有效的。同样的,任何在全局变量中声明的对象,都不是 Lua 认为的垃圾,即使你的程序中根本没有用到他们。这两种情况下,你应当自己处理它(你的程序),为这种对象赋 nil 值,防止他们锁住其他的空闲对象。

然而,简单的清理你的声明并不总是足够的。有些语句需要你和收集器进行额外的合作。一个典型的例子发生在当你想在你的程序中对活动的对象(比如文件)进行收集的时候。那看起来是个简单的任务:你需要做的是在收集器中插入每一个新的对象。然而,一旦对象被插入了收集器,它就不会再被收集!即使没有其他的指针指向它,收集器也不会做什么的。Lua 会认为这个引用是为了阻止对象被回收的,除非你告诉 Lua 怎么做。

Weak 表是一种用来告诉 Lua 一个引用不应该防止对象被回收的机制。一个 weak 引用是指一个不被 Lua 认为是垃圾的对象的引用。如果一个对象所有的引用指向都是weak,对象将被收集,而那些 weak 引用将会被删除。Lua 通过 weak tables 来实现 weak引用:一个 weak tables 是指所有引用都是 weak 的 table。这意味着,如果一个对象只存在于 weak tables 中,Lua 将会最终将它收集。

表有 keys 和 values,而这两者都可能包含任何类型的对象。在一般情况下,垃圾收集器并不会收集作为 keys 和 values 属性的对象。也就是说,keys 和 values 都属于强引用,他们可以防止他们指向的对象被回收。在一个 weak tables 中,keys 和 vaules 也可能是 weak 的。那意味着这里存在三种类型的 weak tables:weak keys 组成的 tables;weak values 组成的 tables;以及纯 weak tables 类型,他们的 keys 和 values 都是 weak 的。与table 本身的类型无关,当一个 keys 或者 vaule 被收集时,整个的入口(entry)都将从这个 table 中消失。

表的 weak 性由他的 metatable 的__mode 域来指定的。在这个域存在的时候,必须是个字符串:如果这个字符串包含小写字母‘k’,这个 table 中的 keys 就是 weak 的;如果这个字符串包含小写字母‘v’,这个 table 中的 vaules 就是 weak 的。下面是一个例子,虽然是人造的,但是可以阐明 weak tables 的基本应用:

a = {}
b = {}
setmetatable(a, b)
b.__mode = \"k\" -- now 'a' has weak keys
key = {} -- creates first key
a[key] = 1
key = {} -- creates second key
a[key] = 2
collectgarbage() -- forces a garbage collection cycle
for k, v in pairs(a) do print(v) end
--> 2

在这个例子中,第二个赋值语句 key={}覆盖了第一个 key 的值。当垃圾收集器工作时,在其他地方没有指向第一个 key 的引用,所以它被收集了,因此相对应的 table 中的入口也同时被移除了。可是,第二个 key,仍然是占用活动的变量 key,所以它不会被收集。

要注意,只有对象才可以从一个 weak table 中被收集。比如数字和布尔值类型的值,都是不会被收集的。例如,如果我们在 table 中插入了一个数值型的 key(在前面那个例子中),它将永远不会被收集器从 table 中移除。当然,如果对应于这个数值型 key 的 vaule被收集,那么它的整个入口将会从 weak table 中被移除。

关于字符串的一些细微差别:从上面的实现来看,尽管字符串是可以被收集的,他们仍然跟其他可收集对象有所区别。 其他对象,比如 tables 和函数,他们都是显示的被创建。比如,不管什么时候当 Lua 遇到{}时,它建立了一个新的 table。任何时候这个function()。。。end 建立了一个新的函数(实际上是一个闭包)。然而,Lua 见到“a”…“b”的时候会创建一个新的字符串?如果系统中已经有一个字符串“ab”的话怎么办?

Lua 会重新建立一个新的?编译器可以在程序运行之前创建字符串么?这无关紧要:这些是实现的细节。因此,从程序员的角度来看,字符串是值而不是对象。所以,就像数值或布尔值,一个字符串不会从 weak tables 中被移除(除非它所关联的 vaule 被收集)。

记忆函数

一个相当普遍的编程技术是用空间来换取时间。你可以通过记忆函数结果来进行优化,当你用同样的参数再次调用函数时,它可以自动返回记忆的结果。想像一下一个通用的服务器,接收包含 Lua 代码的字符串请求。每当它收到一个请求,它调用 loadstring 加载字符串,然后调用函数进行处理。

然而,loadstring 是一个“巨大”的函数,一些命令在服务器中会频繁地使用。不需要反复调用 loadstring 和后面接着的 closeconnection(),服务器可以通过使用一个辅助 table 来记忆 loadstring 的结果。在调用 loadstring 之前,服务器会在这个 table 中寻找这个字符串是否已经有了翻译好的结果。如果没有找到,那么(而且只是这个情况)服务器会调用 loadstring 并把这次的结果存入辅助 table。我们可以将这个操作包装为一个函数:

local results = {}
function mem_loadstring (s)
if results[s] then -- result available?
 return results[s] -- reuse it
else
 local res = loadstring(s) -- compute new result
 results[s] = res -- save for later reuse
 return res
end
end

这个方案的存储消耗可能是巨大的。尽管如此,它仍然可能会导致意料之外的数据冗余。尽管一些命令一遍遍的重复执行,但有些命令可能只运行一次。渐渐地,这个 table积累了服务器所有命令被调用处理后的结果;早晚有一天,它会挤爆服务器的内存。一个 weak table 提供了对于这个问题的简单解决方案。如果这个结果表中有 weak 值,每次的垃圾收集循环都会移除当前时间内所有未被使用的结果(通常是差不多全部):

local results = {}
setmetatable(results, {__mode = \"v\"}) -- make values weak
function mem_loadstring (s)
 ... -- as before
事实上,因为 table 的索引下标经常是字符串式的,如果愿意,我们可以将 table 全部置 weak:
setmetatable(results, {__mode = \"kv\"})

最终结果是完全一样的。

记忆技术在保持一些类型对象的唯一性上同样有用。例如,假如一个系统将通过tables 表达颜色,通过有一定组合方式的红色,绿色,蓝色。一个自然颜色调色器通过每一次新的请求产生新的颜色:

function createRGB (r, g, b)
return {red = r, green = g, blue = b}
end
使用记忆技术,我们可以将同样的颜色结果存储在同一个 table 中。为了建立每一种颜色唯一的 key,我们简单的使用一个分隔符连接颜色索引下标:
local results = {}
setmetatable(results, {__mode = \"v\"}) -- make values weak
function createRGB (r, g, b)
local key = r .. \"-\" .. g .. \"-\" .. b
if results[key] then return results[key]
else
 local newcolor = {red = r, green = g, blue = b}
 results[key] = newcolor
 return newcolor
end
end

一个有趣的后果就是,用户可以使用这个原始的等号运算符比对操作来辨别颜色,因为两个同时存在的颜色通过同一个的 table 来表达。要注意,同样的颜色可能在不同的时间通过不同的 tales 来表达,因为垃圾收集器一次次的在清理结果 table。然而,只要给定的颜色正在被使用,它就不会从结果中被移除。所以,任何时候一个颜色在同其他颜色进行比较的时候存活的够久,它的结果镜像也同样存活。

发布了290 篇原创文章 · 获赞 153 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39885372/article/details/104415978
今日推荐