刚从超市回来,吃了一包樱桃很满足,哈哈,就接着跟着云大哥看看lua table部分的源码:
table是lua里唯一暴漏给client的数据结构,肯定是大神们经过精心设计的。对于使用者简单易用,木有STL那么多容器,那table是如何被操作呢,我不怎么喜欢一开始就问“table是如何实现”,因为了解了使用场景才有兴趣了解它的内部构造。
LUA_API void lua_createtable (lua_State *L, int narray, int nrec) {
Table *t;
lua_lock(L);
luaC_checkGC(L);
t = luaH_new(L);
sethvalue(L, L->top, t);
api_incr_top(L);
if (narray > 0 || nrec > 0)
luaH_resize(L, t, narray, nrec);
lua_unlock(L);
}
lua提供lua_createtable这个API,C里可以调用这个API创建table:
里面声明了Table这个数据结构,然后锁定luaState,检查GC,然后创建table,放进stack,解锁。
首先来看Table这个数据结构:
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;
lsizenode是node节点数目的对数;sizearray表示array数组的数目。
大家肯定还不怎么清楚Node是什么,以及用来做什么的:
typedef struct Node {
TValue i_val;
TKey i_key;
} Node;
是包含了key,value的结构。
而其实
TValue是指 Value value_; int tt_的数据结构;
而TKey是指
typedef union TKey {
struct {
TValuefields;
struct Node *next; /* for chaining */
} nk;
TValue tvk;
} TKey;
的数据结构。
1、然后我们看如何创建table数据结构:
Table *luaH_new (lua_State *L) {
Table *t = &luaC_newobj(L, LUA_TTABLE, sizeof(Table), NULL, 0)->h;
t->metatable = NULL;
t->flags = cast_byte(~0);
t->array = NULL;
t->sizearray = 0;
setnodevector(L, t, 0);
return t;
}
首先申请内存,把相关域初始化,这个setnodevector是做什么呢?
static void setnodevector (lua_State *L, Table *t, int size) {
int lsize;
if (size == 0) { /* no elements to hash part? */
t->node = cast(Node *, dummynode); /* use common `dummynode' */
lsize = 0;
}
else {
int i;
lsize = luaO_ceillog2(size);
if (lsize > MAXBITS)
luaG_runerror(L, "table overflow");
size = twoto(lsize);
t->node = luaM_newvector(L, size, Node);
for (i=0; i<size; i++) {
Node *n = gnode(t, i);
gnext(n) = NULL;
setnilvalue(gkey(n));
setnilvalue(gval(n));
}
}
t->lsizenode = cast_byte(lsize);
t->lastfree = gnode(t, size); /* all positions are free */
}
如果是size是0,就只是设置为dummynode;
否则检测长度是否过大,接着为将node节点链接到新分配的vector上,这里会分配size个node;然后就是遍历node vector作初始化,最后设置lsizenode大小和当前freenode。
2、那如何删除table呢?(C语言一定要记得哟,不然会泄,哈哈),lua代码风格很好,对于函数命名很规范,比如申请内存是luaH_new,那释放内存就会死luaH_free:
void luaH_free (lua_State *L, Table *t) {
if (!isdummy(t->node))
luaM_freearray(L, t->node, cast(size_t, sizenode(t)));
luaM_freearray(L, t->array, t->sizearray);
luaM_free(L, t);
}
当然如果是dummy就没有node数组,不是才要先释放之前分配的node数组,然后释放array数组,最后释放table内存。
3、table使用时怎么实现的呢?
应该有set,get方法。嗯,那是一定的,首先看下如何set的:
LUA_API void lua_rawset (lua_State *L, int idx) {
StkId t;
lua_lock(L);
api_checknelems(L, 2);
t = index2addr(L, idx);
api_check(L, ttistable(t), "table expected");
setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1);
invalidateTMcache(hvalue(t));
luaC_barrierback(L, gcvalue(t), L->top-1);
L->top -= 2;
lua_unlock(L);
}
我忽略了细节,直接看lua内部如何设置key,value的:
/*
** beware: when using this function you probably need to check a GC
** barrier and invalidate the TM cache.
*/
TValue *luaH_set (lua_State *L, Table *t, const TValue *key) {
const TValue *p = luaH_get(t, key);
if (p != luaO_nilobject)
return cast(TValue *, p);
else return luaH_newkey(L, t, key);
}
这里会首先查询,如果查到就直接返回value,还是调用luaH_get。
没有则新建一个key;
/*
** main search function
*/
const TValue *luaH_get (Table *t, const TValue *key) {
switch (ttype(key)) {
case LUA_TSHRSTR: return luaH_getstr(t, rawtsvalue(key));
case LUA_TNIL: return luaO_nilobject;
case LUA_TNUMBER: {
int k;
lua_Number n = nvalue(key);
lua_number2int(k, n);
if (luai_numeq(cast_num(k), n)) /* index is int? */
return luaH_getint(t, k); /* use specialized version */
/* else go through */
}
default: {
Node *n = mainposition(t, key);
do { /* check whether `key' is somewhere in the chain */
if (luaV_rawequalobj(gkey(n), key))
return gval(n); /* that's it */
else n = gnext(n);
} while (n);
return luaO_nilobject;
}
}
从注释可以看出luaH_get是主要的table的搜索函数。这里会根据可以的数据类型分别查找。
因为lua中lua_TValue有个int型的tt字段表示数据类型。
可以看到有 短字符串 空类型 数字 其他 四种分类查询,那我们分别看看:
1)查询字符串
/*
** search function for short strings
*/
const TValue *luaH_getstr (Table *t, TString *key) {
Node *n = hashstr(t, key);
lua_assert(key->tsv.tt == LUA_TSHRSTR);
do { /* check whether `key' is somewhere in the chain */
if (ttisshrstring(gkey(n)) && eqshrstr(rawtsvalue(gkey(n)), key))
return gval(n); /* that's it */
else n = gnext(n);
} while (n);
return luaO_nilobject;
}
首先通过hash拿到一个node桶,然后遍历桶里的node,如果key是字符串,且key完全相同就返回value,否则返回nil。
2)数字key查询:
/*
** search function for integers
*/
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;
}
}
如果key在table的数组表示范围内,就直接返回下标,这里看下具体计算的时候要key-1,所以清楚了客户调用lua table的时候要从1开始,0是非法的。
如果没在数组部分就肯定在散列表,因为table只有这两个部分在存储key,value;还是先散列,然后遍历查询,跟之前的方法一样,我在想 这些方法用C++写,肯定会写成模板,哈哈。
3)然后也是拿到hash值,遍历查询。
那新建key都做了写什么工作呢?
/*
** inserts a new key into a hash table; first, check whether key's main
** position is free. If not, check whether colliding node is in its main
** position or not: if it is not, move colliding node to an empty place and
** put new key in its main position; otherwise (colliding node is in its main
** position), new key goes to an empty position.
*/
TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
Node *mp;
if (ttisnil(key)) luaG_runerror(L, "table index is nil");
else if (ttisnumber(key) && luai_numisnan(L, nvalue(key)))
luaG_runerror(L, "table index is NaN");
mp = mainposition(t, key);
if (!ttisnil(gval(mp)) || isdummy(mp)) { /* main position is taken? */
Node *othern;
Node *n = getfreepos(t); /* get a free place */
if (n == NULL) { /* cannot find a free place? */
rehash(L, t, key); /* grow table */
/* whatever called 'newkey' take care of TM cache and GC barrier */
return luaH_set(L, t, key); /* insert key into grown table */
}
lua_assert(!isdummy(n));
othern = mainposition(t, gkey(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;
}
}
setobj2t(L, gkey(mp), key);
luaC_barrierback(L, obj2gco(t), key);
lua_assert(ttisnil(gval(mp)));
return gval(mp);
}
这个函数稍复杂。
1、检测是否为nil,是则报错返回;如果是数字,若是未定义数字也错误返回
2、获取key的主位置,内部就是根据key类型不同获取hash值
3、如果主位置被占,则获取table node表的一个空闲位置
若没有空闲位置,则增大table,然后重新设置
若有空闲位置,检测主位置上的node是否鸠占鹊巢,如果是,就给这丫分配一个适当位置;
如果不是,就是要解决冲突,放在下一个就行了;
最后返回value。
ok,暂时分析到这里吧。表示lua真的很流弊。
参考 云风《lua源码赏析》