快速掌握Lua 5.3 —— "Weak Tables"以及数学库

Q:“引用”和“引用计数”?

A:“引用”既为对象的名字。比如a = {},名叫a的变量存储的值是一个”table”,”table”的名字是a;同时还可以说变量a存储着”table”的“引用”。
当“(强)引用”被创建时,对象的“引用计数”加1,只要对象的“引用计数”不为0,对象就不会被销毁,Lua的“垃圾回收系统”只回收那些“引用计数”为0的对象。

Q:“强引用”和“弱引用”?

A:当对象被创建时会导致对象的引用计数加1的引用为“强引用”。
默认情况下,Lua中的对象在被创建时都是“强引用”,

-- t中保存了"table"的“强引用”。
t = {"one", "two"}
-- t保存了新的"table"的“强引用”,原先"table"的引用计数减为0,将被销毁。
t = {"three", "four"}

顾名思义,当对象被创建时不会导致对象的引用计数加1的引用为“弱引用”。因为其特性,“弱引用”不会阻止“垃圾回收系统”回收对象。通过”Weak Tables”,Lua支持”table”中的”key”或是”value”或是两者作为“弱引用”存储。
metatable__mode域可以控制具体的操作。这个域必须存储的是一个字符串,如果字符串中包含'k'(一定要是小写的),那么”weak table”中对所有的”key”都使用“弱引用”;如果字符串中包含'v'(小写),那么”weak table”中对所有的”value”都使用“弱引用”,

a = {}
b = {}
setmetatable(a, b)    -- 将"b"设置为"a"的"metatable"。
b.__mode = "k"        -- now 'a' has weak keys
key = {}              -- creates first key
a[key] = 1
key = {}              -- creates second key
a[key] = 2
collectgarbage()      -- 强制让Lua回收垃圾。
for k, v in pairs(a) do print(v) end --> 2

这个例子中,第二个key = {}使得变量”key”存储了新的”table”,而原先的”table”没有强引用对其引用了(只有”weak table”对其引用,而且还是弱引用)。所以当调用collectgarbage()时,第一个”table”被垃圾收集器收集,所以”table”中只剩下了第二个”table”(这第二个”table”由变量key对其强引用)。

Q:”weak table”的实际应用?

A:编程中一项通用的技术就是“空间换时间”。为了给函数提速,你可以将函数的参数与其结果作为映射存储起来。当下次有相同的参数时,直接取出结果。
想象一个用Lua写的服务器,他接收请求,请求均是以字符串组成。每一次接收到一个请求,他就调用一次loadstring()loadstring()是一个开销很大的函数,并且有的请求对于服务器来说会非常的频繁,比如closeconnection()。为了避免每次收到这种频繁的请求都要调用一次loadstring(),服务器可以使用一张辅助table来记录请求字符串与其用loadstring()得到的结果之间的映射。在调用loadstring()之前,服务器首先查看请求的命令是否存储在辅助表中,如果存在,则直接取出结果,如果不存在,才调用loadstring(),并在调用完成之后将参数与结果的映射存储在辅助表中。

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 = "kv"})  -- make keys and values weak
function mem_loadstring (s)
   ...    -- as before

Q:如何使用”weak table”存储”table”的默认值?

A:1、使用”weak table”将”table”与”default value”关联,

local defaults = {}    -- "table"与"default value"之间的映射。
setmetatable(defaults, {__mode = "k"})    -- "table"可以被回收。
local mt = {__index = function (t) return defaults[t] end}
function setDefault (t, d)
    defaults[t] = d
    setmetatable(t, mt)
end

2、使用”weak table”将”default value”与获取”default value”的方法关联,

local metas = {}    -- "default value"与获取"default value"的方法之间的映射。
setmetatable(metas, {__mode = "v"})    -- 获取"default value"的方法可以被回收。
function setDefault (t, d)
    local mt = metas[d]
    if mt == nil then
        -- 获取"default value"的方法。
        mt = {__index = function () return d end}
        metas[d] = mt
    end
    setmetatable(t, mt)
end

Q:三角函数、幂函数、对数函数、平方函数、取整取余函数、极值函数、绝对值函数、数值类型函数、无符号数值比较函数、值?

A:

-- 三角函数
math.sin(x), math.cos(x), math.tan(x), math.asin(x), math.acos(x), math.atan(x), 
math.deg(x)    -- 弧度转角度。
math.rad(x)    -- 角度转弧度。
-- 所有三角函数的参数或者返回值默认是以弧度为单位。如果你想以角度为单位,可以自行转换。
local sin, asin, ... = math.sin, math.asin, ...
local deg, rad = math.deg, math.rad
math.sin = function (x) return sin(rad(x)) end
math.asin = function (x) return deg(asin(x)) end
...

-- 幂函数、对数函数、平方函数
math.exp(x)    -- 返回自然对数"e"的"x"幂。
math.log(x [, base])    -- "x"基于"base"的对数,"base"默认是自然对数"e"。
math.sqrt(x)    -- 返回"x"的平方根。也可以使用"x ^ 0.5"计算。

-- 取整取余函数
math.floor(x)    -- 返回小于等于"x"的最大整数。
math.ceil(x)    -- 返回大于等于"x"的最小整数。
math.modf(x)    -- 返回"x"的整数部分和小数部分。
math.fmod(x, y)    -- 返回参数"x / y"的余数。
math.tointeger(x)    -- 将"x"转换为整数(如果可以)。

-- 极值函数
math.max(x, ···), math.min(x, ···)

-- 绝对值函数
math.abs(x)

-- 数值类型函数
math.type(x)    -- 如果"x"是数值则返回其类型("integer", "float"),否则返回"nil"。

-- 无符号数值比较函数
-- 如果整数"m"和"n"以无符号整数形式比较,并且"m < n"则返回"true",否则返回"false"。 
math.ult(m, n)

-- 值
math.pi    -- 圆周率。
math.maxinteger    -- 整数的最大值。
math.huge    -- C语言中定义的宏"HUGE_VAL"的值,一个大于任何数值的数(数值太大,无法表示时使用)。
math.mininteger    -- 整数的最小值。

Q:如何使用随机数?

A:

--[[ 产生伪随机数,有三种调用方式: 1、不带参数,将产生"[0,1)"范围内的随机数。 2、带一个参数"m",将产生"[1, m]"范围内的随机数。 3、带两个参数"m"和"n",将产生[m, n]范围内的随机数。]]
math.random([m [, n]])
math.randomseed(x)    -- 设置随机数种子。

附加:

1、Lua使用“引用计数”机制供“垃圾回收系统”自动回收无用的垃圾。Lua的“垃圾回收系统”将你从内存管理的负担中解放出来,更重要的是你不用担心与内存管理相关的”bugs”,比如野指针和内存泄露。
但即使是“最聪明”的“垃圾回收系统”也不可能让程序员完全不用关注内存管理的问题。“垃圾回收系统”只能收集他所认为的垃圾,他不知道你所认为的垃圾有哪些。一个典型的例子是栈,当你使用一个数组加上一个存储栈顶索引的变量实现一个栈时,你知道数组中从栈底到栈顶之间的数据是有效的,但Lua不知道。当你pop一个数据,然后将栈顶索引减1的时候,刚被pop的数据依旧在数组中,而你知道他没用了,可是Lua不知道。类似的,任何存储在全局变量中的数据对于Lua来说都不是垃圾(即使有可能你的程序不会再使用它们)。在这两种情况下,这取决于你(的程序)将这些变量赋值为nil
2、要注意,只有对象才可以从一个”weak table”中被收集。比如数字或字符串都是不会被收集的。此外Lua中的函数也是对象,他如果作为”weak table”中的”key”也可以被收集,

a = {}
b = {}
setmetatable(a, b)    -- 将"b"设置为"a"的"metatable"。
b.__mode = "k"        -- now 'a' has weak keys
a[1] = 6
a["a" .. "b"] = 7
foo = function() print() end    -- first key, ignore "print()".
a[foo] = 9
foo = function() print() end    -- second key, ignore "print()".
a[foo] = 10
collectgarbage()      -- 强制让Lua回收垃圾。
for k, v in pairs(a) do io.write(v, " ") end    --> 6 10 7
print()

关于字符串的一些细微差别:从上面的例子来看,尽管字符串是可以被收集的,但他
们仍然跟其他可收集对象有所区别。其他对象,比如”tables”和函数,他们都是显式的被创建。比如,不管什么时候当Lua遇到{}时,它建立了一个新的”table”;任何时候遇到function() ... end时,建立了一个新的函数(实际上是一个”Closure”)。
然而,当Lua见到"a" .. "b"时会创建一个新的字符串?如果系统中已经有一个字符串"ab"的话怎么办?Lua会重新建立一个新的?编译器可以在程序运行之前创建字符串吗?这无关紧要:这些是实现的细节。因此,从程序员的角度来看,字符串是值而不是对象。所以,就像数值或布尔值,一个字符串不会从”weak tables”中被移除(除非它所关联的”vaule”被收集)。
3、math.randomseed()的参数一般为os.time()以产生随机的种子,

math.randomseed(os.time())

你可能感兴趣的:(lua)