刚从超市回来,吃了一包樱桃很满足,哈哈,就接着跟着云大哥看看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); }
里面声明了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;
而其实
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; }
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 */ }
否则检测长度是否过大,接着为将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); }
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); }
/* ** 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); }
没有则新建一个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; } }
因为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; }
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; } }
如果没在数组部分就肯定在散列表,因为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源码赏析》
原文:http://blog.csdn.net/boyxiaolong/article/details/24122393