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