細談一下lua裡很多人有疑問的table長度問題。
<pre name="code" class="cpp">1.> tbl = {1,2,3} > print(#tbl)3> 2.> tbl = {1,nil,3}> print(#tbl) 3> 3.> tbl = {1,nil,3,nil}> print(#tbl) 1>
情況1正常,情況2有點不正常,情況3很不正常,好,先上源碼。
int luaH_getn (Table *t) { unsigned int j = t->sizearray; if (j > 0 && ttisnil(&t->array[j - 1])) { /* there is a boundary in the array part: (binary) search for it */ unsigned int i = 0; while (j - i > 1) { unsigned int m = (i+j)/2; if (ttisnil(&t->array[m - 1])) j = m; else i = m; } return i; } /* else must find a boundary in hash part */ else if (isdummy(t->node)) /* hash part is empty? */ return j; /* that is easy... */ else return unbound_search(t, j);}
此函數是用來求知table的長度,理解這個函數對於lua table的整個原理有很大協助。
條件1:j>0而且數組部分最後一個為空白,則進入下面的二分尋找,此二分尋找就是找到一個i不為空白,j為空白的的兩個索引,當j-i>1就跳出來返回i.
條件2:j<=0或者數組最後一個不為空白,而且沒有hash部分,此時,沒辦法,沒得統計,折中返回數組的長度的。
條件3:j<=0或者數組最後一個不為空白,有hash部分,進入到unbound_search,從字面意思,就是在一個亂的區間尋找,此函數最終也是一個二分尋找。
先理解條件1,最上面的情況三就符合條件1的情形,好,直接上gdb.下面的截圖,慢慢看。
數組長度j = t->t->sizearray = 4,j>0,把最後一個元素列印出來:
tt_ = 0表示此元素為nil值,符合條件1,進入到二分尋找,此二分尋找就是找到一個左邊不為nil,右邊為nil時的左邊索引。
i = 0,j = 4,m = (i+j)/2 = 2,m-1 = 1,索引為1的元索是nil,0才是1,所以j = m = 2;
i = 0,j = 2,m = (i+j)/2 = 1,m-1 = 0,索引為0的元索是1,所以i = m = 1;
i = 1,j=2,退出迴圈,長度為1.
可以得知,就算僅僅統計只有數組部分的長度也有可能不正常。
條件2製造很簡單,跟情況2符合,看gdb:
tbl = {1,nil,3},此table度度j = t->sizearray = 3,最後一個元素tt_ = 3,n = 3,就表示此value為number類型,值為3,也就是tbl最後一個元素不為nil,t->node 而且isdummy,hash部分也為空白,折中直接返回數組長度3.
先看unbound_search函數實現:
static int unbound_search (Table *t, unsigned int j) { unsigned int i = j; /* i is zero or a present index */ j++; /* find `i' and `j' such that i is present and j is not */ while (!ttisnil(luaH_getint(t, j))) { i = j; j *= 2; if (j > cast(unsigned int, MAX_INT)) { /* overflow? */ /* table was built with bad purposes: resort to linear search */ i = 1; while (!ttisnil(luaH_getint(t, i))) i++; return i - 1; } } /* now do a binary search between them */ while (j - i > 1) { unsigned int m = (i+j)/2; if (ttisnil(luaH_getint(t, m))) j = m; else i = m; } return i;}const TValue *luaH_getint (Table *t, int key) { /* (1 <= key && key <= t->sizearray) */ if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray)) return &t->array[key-1]; else { lua_Number nk = cast_num(key); Node *n = hashnum(t, nk); do { /* check whether `key' is somewhere in the chain */ if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) return gval(n); /* that's it */ else n = gnext(n); } while (n); return luaO_nilobject; }}
此函數傳入一個數組長度,也就是認為數組滿了,才進入到此函數。
第一個while就是一直尋找到一個j,而且以此j為key,不存在於table,包括數組部分和hash部分(luaH_getint的實現),側退出第一個while,如果中途j>MAX_INT,沒辦法,此j已經好大了,只能resort to linear search了,就是轉到線性尋找,一個一個找到一個為空白就返回I-1為table的長度。
在第一個while找到j之後,此時i和j就是左邊不為nil,右邊為nil的兩個值,在此兩值之前再二分尋找,尋找到一個最小的左邊不為nil,右邊為nil的兩個值,返回前者。
好,製造此情況,看gdb:
tbl = {1,nil,3,qs = 19891103},數組長度為3,最後一個元素不為nil,
有hash部分,見qs = 19891103,進入到一個雜亂無章的區間內尋找(unbound_search),在進入第一個while時i=3,j=4,luaH_getint的時候
,以key為4在數組部分和hash部分都不存在的。直接進入到以左邊為3,右邊為4的二分尋找(此二分尋找上面有說)。直接就返回3為table的長度。
可見,進入到unbound_search的時候,此時獲得資料長度,有可能靠譜,大部分情況都是不靠譜的。
如果數組部分是滿的而且沒有空洞情況,而且存在於hash部分的key跟數組部分是連續起來的,此時擷取長度是達到預期的,有hash部分的時候就要進入到unbound_search尋找,假如tbl = {1,2,3,[4] = 4},此時,對外來看,此tbl確實是一個數組,但在記憶體裡,並不是,1,2,3在數組部分,4在hash部分,所以在進入unbound_search,此時擷取長度也是正常的.