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). spa

  三點理解:(1)gc自動運行,也能夠手動調用;(2)自動收集的目標是引用計數爲0的對象;(3)dead objects:不能訪問到的對象,沒有引用指向它了,固然就是訪問不到的了,也就等同於垃圾內存了。code

 

  weak table的定義:對象

  (1)weak表是一個表,它擁有metatable,而且metatable定義了__mode字段;blog

  (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,但經常使用默認值只有那麼幾種,建議選擇第二種方法,不然就選擇第一種方法。

相關文章
相關標籤/搜索