• Lua中的weak表——weak table


      弱表(weak table)是一个很有意思的东西,像C++/Java等语言是没有的。弱表的定义是:A weak table is a table whose elements are weak references,元素为弱引用的表就叫弱表。有弱引用那么也就有强引用,有引用那么也就有非引用。我们先要厘这些基本概念:变量、值、类型、对象。

      (1)变量与值:Lua是一个dynamically typed language,也就是说在Lua中,变量没有类型,它可以是任何东西,而值有类型,所以Lua中没有变量类型定义这种东西。另外,Lua中所有的值都是第一类值(first-class values)。

      (2)Lua有8种基本类型:nil、boolean、number、string、function、userdata、thread、table。其中Nil就是nil变量的类型,nil的主要用途就是一个所有类型之外的类型,用于区别其他7中基本类型。

      (3)对象objects:Tables、functins、threads、userdata。对于这几种值类型,其变量皆为引用类型(变量本身不存储类型数据,而是指向它们)。赋值、参数传递、函数返回等都操作的是这些值的引用,并不产生任何copy行为。

       

      Lua的垃圾回收机制:gc是很多语言的常见机制,让程序员拜托复杂易出错的内存管理。

      定义:Lua manages memory automatically by running a garbage collector to collect all dead objects (that is, objects that are no longer accessible from Lua). 

      三点理解:(1)gc自动运行,也可以手动调用;(2)自动收集的目标是引用计数为0的对象;(3)dead objects:不能访问到的对象,没有引用指向它了,当然就是访问不到的了,也就等同于垃圾内存了。

      weak table的定义:

      (1)weak表是一个表,它拥有metatable,并且metatable定义了__mode字段;

      (2)weak表中的引用是弱引用(weak reference),弱引用不会导致对象的引用计数变化。换言之,如果一个对象只有弱引用指向它,那么gc会自动回收该对象的内存。

      (3)__mode字段可以取以下三个值:k、v、kv。k表示table.key是weak的,也就是table的keys能够被自动gc;v表示table.value是weak的,也就是table的values能被自动gc;kv就是二者的组合。任何情况下,只要key和value中的一个被gc,那么这个key-value pair就被从表中移除了( In any case, if either the key or the value is collected, the whole pair is removed from the table)。

      对于普通的强引用表,当你把对象放进表中的时候,就产生了一个引用,那么即使其他地方没有对表中元素的任何引用,gc也不会被回收这些对象。那么你的选择只有两种:手动释放表元素或者让它们常驻内存。

    strongTable = {}
    strongTable[1] = function() print("i am the first element") end
    strongTable[2] = function() print("i am the second element") end
    strongTable[3] = {10, 20, 30}
    
    print(table.getn(strongTable))            -- 3
    collectgarbage()                        
    print(table.getn(strongTable))            -- 3

      但是,在编程环境中,有时你并不确定手动给一个键值赋nil的时机,而是需要等所有使用者用完以后进行释放,在释放以前,是可以访问这个键值对的。这种时候,weak表就派上用场了。关于weak table的理解,看下面这个小例子:

    weakTable = {}
    weakTable[1] = function() print("i am the first element") end
    weakTable[2] = function() print("i am the second element") end
    weakTable[3] = {10, 20, 30}
    
    setmetatable(weakTable, {__mode = "v"})        -- 设置为弱表
    
    print(table.getn(weakTable))                -- 3
    
    ele = weakTable[1]                    -- 给第一个元素增加一个引用
    collectgarbage()
    print(table.getn(weakTable))               -- 1,第一个函数引用为1,不能gc
    
    ele = nil                             -- 释放引用
    collectgarbage()
    print(table.getn(weakTable))                -- 0,没有其他引用了,全部gc

      当然在实际的代码过程中,我们不一定需要手动collectgarbage,因为该函数是在后台自动运行的,它有自己的运行周期和规律,对编程者来说是透明的。

      注意:只有拥有显示构造的对象类型会被自动从weak表中移除,值类型boolean、number是不会自动从weak中移除的。而string类型虽然也由gc来负责清理,但是string没有显示的构造过程,因此也不会自动从weak表中移除,对于string的内存管理有单独的策略。

      基于weak表的简单应用:

      (1)记忆函数:一个相当普遍的编程技术是用空间来换取时间。你可以通过记忆函数结果来进行优化,当你用同样的参数再次调用函数时,它可以自动返回记忆的结果。将函数的输入和输出分别作为key和value放在一个weak table里面,调用函数之前先查看有无现成的结果,有就返回,没有就调用函数,然后将结果存入表中。由于是weak table,此表会定期自动清理掉不再有引用的键值对。

      (2)关联对象属性:Lua本身使用这种技术来保存数组的大小。table库提供了一个函数来设定数组的大小,另一个函数来读取数组的大小。当你设定了一个数组的大小,Lua 将这个尺寸保存在一个私有的weak table,索引就是数组本身,而value就是它的尺寸。

      同样的,当我们需要给任一对象添加一个属性的时候,可以在外部单独做一弱key表,然后以对象为key值,属性值为value。这样即可以方便的访问这个属性,也不影响该对象的释放。而且对象本身没任何修改,能很好的保持对象本身的独立性。

       (3)带有默认值的表:

      有两种实现方法,第一种方法,使用关联对象属性的方法,将表作为key,默认值作为value,存到一个弱key的weak表中:

    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

      第二种方法,针对不同的metatable来进行优化,对于每一个具体的默认值,生成一个与之对应的metatable,然后以默认值为key,metatable为value,存到一个弱value的weak表中:

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

      两种方式各有利弊,第一种方法对于每一个table都需要添加一个键值对,但是公用一个metatable。第二种方法需要许多个不同的metatable,但拥有相同默认值的table共用一个metatable,并且weak表要比第一种方法小。如果你的代码环境中有很多个table,但常用默认值只有那么几种,建议选择第二种方法,否则就选择第一种方法。

      

  • 相关阅读:
    测试产品
    三年回顾
    测试服务输出业务价值
    慎用重载_2
    慎用重载_1
    ByteBuffer和String的互相转换
    线程较为深的度剖析1
    线程同步的故事描述
    Java线程同步
    TCP关闭过程
  • 原文地址:https://www.cnblogs.com/sifenkesi/p/3850760.html
Copyright © 2020-2023  润新知