table的數據結構
Lua把 table 的儲存分爲數組部分和哈希表部分。數組部分從 1 開始作整數數字索引。
這可以快速高效的訪問。而不能被儲存在數組部分的數據全部放在哈希表中, nil不能
做哈希鍵值。Lua的哈希表的操作時間複雜度爲O(1)。源碼如下:
lobject.h//
typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_byte lsizenode; /* log2 of size of `node' array */
struct Table *metatable;
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
GCObject *gclist;
int sizearray; /* size of `array' array */
} Table;
table結構由三塊連續內存構成。一塊存放整數索引的數組,一塊存放2的整數次冪大小的哈希表。哈希表的最小尺寸爲2的0次冪。對於空的哈希表,用dummynode初始化。
table的算法
Lua實現了四種基本操作:讀、寫、迭代和獲取長度。Lua中並沒有刪除操作,而僅僅是把對應鍵位的值設置爲nil。table中的數組實現和C的數組是一樣,哈希表則是用閉散列實現的。下面是哈希表的具體實現。
table中的哈希表的每一個鍵位都會有一個主位置,當在加入一個新的鍵值對時,會先檢查新的鍵是否和已有鍵衝突,沒有衝突則直接設值。當發生衝突是,會先檢查已有的鍵值是否是主位置,如果是主位置,則將新鍵已一個單鏈表的形式鏈接到主鍵的next域上,否則新鍵佔據當前位置,通過lastfree來從後往前進行查找,找到一個空位後把舊的鍵值對放在這裏。當沒有空的位置時,則觸發rehash方法,重新分配table的大小。當賦nil值的時候,Lua也不會回收空間,而是當準備好的哈希表空間用完的時候,也就是lastfree遞減到哈希表空間頭的時候,觸發rehash操作。源碼如下:
static TValue *newkey (lua_State *L, Table *t, const TValue *key) {
Node *mp = mainposition(t, key);
if (!ttisnil(gval(mp)) || mp == dummynode) {
Node *othern;
Node *n = getfreepos(t); /* get a free place */
if (n == NULL) { /* cannot find a free place? */
rehash(L, t, key); /* grow table */
return luaH_set(L, t, key); /* re-insert key into grown table */
}
lua_assert(n != dummynode);
othern = mainposition(t, key2tval(mp));
if (othern != mp) { /* is colliding node out of its main position? */
/* yes; move colliding node into free position */
while (gnext(othern) != mp) othern = gnext(othern); /* find previous */
gnext(othern) = n; /* redo the chain with `n' in place of `mp' */
*n = *mp; /* copy colliding node into free pos. (mp->next also goes) */
gnext(mp) = NULL; /* now `mp' is free */
setnilvalue(gval(mp));
}
else { /* colliding node is in its own main position */
/* new node will go into free position */
gnext(n) = gnext(mp); /* chain new position */
gnext(mp) = n;
mp = n;
}
}
gkey(mp)->value = key->value; gkey(mp)->tt = key->tt;
luaC_barriert(L, t, key);
lua_assert(ttisnil(gval(mp)));
return gval(mp);
}
rehash 的主要工作是統計當前table中到底有多少有效鍵值對,以及決定數組部分需要開闢多少空間。其原則是最終數組部分的利用率需要超過50%。lua使用一個rehash函數中定義在棧上的nums數組來做這個整數鍵統計工作。這個數組按2的整數冪次來分開統計各個區段間的整數鍵個數。統計過程的實現見numusearray和numusehash函數。最終,computesizes函數計算出不低於50%利用率下,數組空間的大小。同時,還可以得到有多少有效鍵將被儲存在哈希表裏。根據這些統計數據,rehash函數調用luaH_resize 這個 api 來重新調整數組部分和哈希部分的大小,並把不能放在數組裏的鍵值對重新塞入哈希表。源碼如下:
static void rehash (lua_State *L, Table *t, const TValue *ek) {
int nasize, na;
int nums[MAXBITS+1]; /* nums[i] = number of keys between 2^(i-1) and 2^i */
int i;
int totaluse;
for (i=0; i<=MAXBITS; i++) nums[i] = 0; /* reset counts */
nasize = numusearray(t, nums); /* count keys in array part */
totaluse = nasize; /* all those keys are integer keys */
totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */
/* count extra key */
nasize += countint(ek, nums);
totaluse++;
/* compute new size for array part */
na = computesizes(nums, &nasize);
/* resize the table to new computed sizes */
resize(L, t, nasize, totaluse - na);
}
查詢操作 luaH_get 的實現要簡單的多。當查詢鍵爲整數鍵且在數組範圍內時,在數組部分查詢;否則,根據鍵的哈希值去哈希表中查詢。擁有相同哈希值的衝突鍵值對,在哈希表中由 node 的 next域單向鏈起 來,所以遍歷這個鏈表就可以了。源碼如下:
const TValue *luaH_get (Table *t, const TValue *key) {
switch (ttype(key)) {
case LUA_TNIL: return luaO_nilobject;
case LUA_TSTRING: return luaH_getstr(t, rawtsvalue(key));
case LUA_TNUMBER: {
int k;
lua_Number n = nvalue(key);
lua_number2int(k, n);
if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */
return luaH_getnum(t, k); /* use specialized version */
/* else go through */
}
default: {
Node *n = mainposition(t, key);
do { /* check whether `key' is somewhere in the chain */
if (luaO_rawequalObj(key2tval(n), key))
return gval(n); /* that's it */
else n = gnext(n);
} while (n);
return luaO_nilobject;
}
}
}
table的迭代
Lua中table的迭代是通過luaH_next方法實現的,出入上一個鍵,返回下一個鍵值對。這個方法會先在數組部分查找這個key,當超過數組部分後,就會在哈希表中查找,找到後返回對應節點在存儲空間中的下一個節點處的鍵值對。