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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章