lua,一款很輕量級很nice很強大的腳本語言,做為lua中使用最為頻繁的table表,在使用之時還是有頗多的好處與坑的;
下面是大牛 雲風的一片關於lua table的blog,可使得對lua table內在機制 窺測一二;
lua 的整體效率是很高的,其中,它的 table 實現的很巧妙為這個效率貢獻很大。
lua 的 table 充當了數組和映射表的雙重功能,所以在實現時就考慮了這些,讓 table 在做數組使用時盡量少效率懲罰。
lua 是這樣做的。它把一個 table 分成數組段和 hash 段兩個部分。數字 key 一般放在數組段中,沒有初始化過的 key 值全部設置為 nil 。當數字 key 過於離散的時候,部分較大的數字 key 會被移到 hash段中去。這個分割線是以數組段的利用率不低於 50% 為准。 0 和 負數做 key 時是肯定放在 hash 段中的。
string 和 number 都放在一起做 hash ,分別有各自的算法,但是 hash 的結果都在一個數值段中。hash 段采用閉散列方法,即,所有的值都存在於表中。如果hash 發生碰撞,額外的數據記在空閑槽位里,而不額外分配空間存放。當整個個表放滿后,hash 段會擴大,所有段內的數據將被重新 hash ,重新 hash 后,沖突將大大減少。
這種 table 的實現策略,首先保證的是查找效率。對於把 table 當數組使用時將和 C 數組一樣高效。對於 hash 段的值,查找幾乎就是計算 hash 值的過程(其中string 的 hash 值是事先計算好保存的),只有在碰撞的時候才會有少許的額外查找時間,而空間也不至於過於浪費。在 hash 表比較滿時,插入較容易發生碰撞,這個時候,則需要在表中找到空的插槽。lua 在table 的結構中記錄了一個指針順次從一頭向另一頭循序插入來解決空槽的檢索。每個槽點在記錄 next 指針保存被碰撞的 key 的關聯性。
整個來說,這種解決方法是非常不錯的。
關於映射表的實現,我前段時間也做過一個別的研究。貼在留言本上:
<a href="http://www.codingnow.com/2004/board/view.php?paster=777&reply=0">樹表結合的一種映射表實現</a>
<a href="http://www.codingnow.com/2004/board/view.php?paster=776&reply=0">在 vector , map , list 間取得平衡</a>
原文鏈接: http://blog.codingnow.com/2005/10/lua_table.html
即便作為lua 開發蠻久的coder來講,很多東西不親自去考究一下,還不是很清晰的,不如lua table的長度問題就是一個 很奇葩的例子;下面的這些也許你就不是很清楚了;
想要取得lua table長度,有這么幾種方法,table.getn(table_name), #table_name, table.maxn(table_name), 再加上 ipairs(table_name) 和pairs(table_name)遍歷;
根據雲風所寫的文章可以得知,lua的兩種不同的存儲方式,自然的,上述的幾種 取得lua table 長度的幾種方式也 存在區別;
table.maxn(table)
table.maxn()函數返回指定table中所有正數key值中最大的key值. 如果不存在key值為正數的元素, 則返回0. 此函數不限於table的數組部分.
table.getn(table)
返回table中元素的個數
#(table)
返回的是lua table 中key為連續整型數字(抑或是 默認整型)的長度數;
pairs()函數基本和ipairs()函數用法相同, 區別在於pairs()可以遍歷整個table, 即包括數組及非數組部分.
關於對lua table長度的問題,http://blog.csdn.net/dssdss123/article/details/12676329 對於一些奇葩問題的講述還是有些深入的;親測【lua version=5.1.4】,的確如此;
但是還是有些東西需要補充的,對於 #用法,其表現和table.getn()在很多極端的情況下都是類似的;table.maxn(),因為獲取的是table中所有正數key值中最大的key值.可以不連續;
local tblTest = { "this 1", "this 2", [3] = 2, [4] = 5, [5] = 7, "this 3", [10] = 10, } print(table.getn(tblTest)) print(#(tblTest)) print(table.maxn(tblTest)) --=========================== >lua -e "io.stdout:setvbuf 'no'" "filedeal.lua" 5 5 10 --=========================== local tblTest = { "this 1", [3] = 2, [4] = 5, [5] = 7, "this 3", --"adsfasd", [10] = 10, } print(table.getn(tblTest)) print(#(tblTest)) print(table.maxn(tblTest)) --=========================== >lua -e "io.stdout:setvbuf 'no'" "filedeal.lua" 5 5 10 --=========================== local tblTest = { "this 1", [3] = 2, [4] = 5, [5] = 7, --"adsfasd", [10] = 10, } print(table.getn(tblTest)) print(#(tblTest)) print(table.maxn(tblTest)) --=========================== >lua -e "io.stdout:setvbuf 'no'" "filedeal.lua" 1 1 10 --===========================
上述三個tabTest 的不同在於 這都是lua默認的下表是從1開始,有兩個的默認的,使得 [3]=2,這項元素足以將連續性接上,當接不上的時候,因為不連續行,自然打印值有所不同了;
倘若在后面或者該表 其中再墜入一個nil,所輸出來的內容又是不一樣的:所以~不要在lua的table中使用nil值,如果一個元素要刪除,直接remove,不要用nil去代替。
倘若再將元素后面加一項 下表默認的元素,其結果會怎樣呢? 例 如下代碼:
local tblTest = { "this 1", [3] = 2, [4] = 5, [5] = 7, "this 3", --"adsfasd", [10] = 10, } for k , v in ipairs(tblTest) do print(k,v) end --============================= 1 this 1 2 this 3 3 2 4 5 5 7 --============================= local tblTest = { "this 1", [3] = 2, [4] = 5, [5] = 7, "this 3", "this 4", --"adsfasd", [10] = 10, } for k , v in ipairs(tblTest) do print(k,v) end --============================= 1 this 1 2 this 3 3 this 4 4 5 5 7 --=============================
由此可見,lua 默認的下表值會將顯示的覆蓋,即便是 再調整下順序 也是如是,至於為何如此,不理解,有待參悟【歡求 大神指正】;
這樣的意外在lua中 挺多的,只要明白了 基本的原理,倒也不足為奇了;
至於lua table的遍歷 可參見 http://rangercyh.blog.51cto.com/1444712/1032925 這這篇文章;講的挺詳細的;
另外: 關於lua table的其他小問題:
1, 配置lua table 元素之間,以”,“ 或者”;“完全是一樣的【可參見lua手冊】,看你的愛好了,推薦的是:用分好可以作為 元素類型的不同而分割開顯示下;
2,不要在lua的table中使用nil值,如果一個元素要刪除,直接remove,不要用nil去代替。
3,判斷lua table 是否為nil 不能用 if a == {} then 【錯誤的】(這樣的結果就是a == {}永遠返回false,是一個邏輯錯誤。因為這里比較的是table a和一個匿名table的內存地址。);
if table.maxn(a) == 0 then 【錯誤的】這樣做不保險啊,除非table的key都是數字,而沒有hash部分。
if #(a) == 0 then 也是不靠譜的,除非你能保證沒人這樣寫這個table like this:tab = {nil,1,nil;} 用#tab print出來 的確是0, 能說此tab是nil的?
可以使用lua內置的next來判斷; if next(a) == 0 then ;
4,應該盡量使用 local 變量而非 global 變量。這是 Lua 初學者最容易犯的錯誤。 global 變量實際上是放在一張全局的 table 里的。 global 變量實際上是利用一個 string (變量名作 key) 去訪問這個 table 。 雖然[InterWiki]Lua5 的 table 效率很高 ,但是相對於 local 變量,依然有很大的效率損失。 local 變量是直接通過 Lua 的堆棧訪問的。有些 global 變量的訪問是不經意的,比如雙重for中使用的pairs或者ipirs(全局函數),如果在使用循環外層 local pairs=pairs會對性能有些不同層次的提升;
5, 警惕臨時變量 字符串的連接操作,會產生新的對象。這是由 lua 本身的 string 管理機制導致的。lua 在 VM 內對相同的 string 永遠只保留一份唯一 copy ,這樣,所有字符串比較就可以簡化為地址比較。這也是 lua 的 table 工作很快的原因之一。這種 string 管理的策略,跟 java 等一樣,所以跟 java 一樣,應該盡量避免在循環內不斷的連接字符串,比如 a = a..x 這樣。每次運行,都很可能會生成一份新的 copy 。
6, 同樣,記住,每次構造一份 table 都會多一份 table 的 copy 。比如在 lua 里,把平面坐標封裝成 { x, y } 用於參數傳遞,就需要考慮這個問題。每次你想構造一個坐標對象傳遞給一個函數,{ 10,20 } 這樣明確的寫出,都會構造一個新的 table 出來。要么,我們想辦法考慮 table 的重用;要么,干脆用 x,y 兩個參數傳遞坐標。 同樣需要注意的是以 function foo (...) 這種方式定義函數, ... 這種不定參數,每次調用的時候都會被定義出一個 table 存放不定數量的參數。 這些臨時構造的對象往往要到 gc 的時候才被回收,過於頻繁的 gc 有時候正是效率瓶頸。
7,【未完待續...】