table在lua中是一个重要的数据结构,使用起来非常灵活,可以用它表示通常的array,map,set等结构。在分析源码之前,先看看table的一些特性。
(1). table是个key-value数组,索引key可以是数字,也可以是字符串。
(2). Key为数字时,默认下表从1开始(c/c++从0开始)
(3). table大小可以动态变化
(4). Lua5.2.3中table有7个成员函数(concat,insert,move,pack,remove,sort,unpack)
- Lua中table定义如下(lobject.h)。
typedef union TKey {
struct {
TValuefields;
struct Node *next; /* for chaining */
} nk;
TValue tvk;
} TKey;
typedef struct Node {
TValue i_val;
TKey i_key; //保存有下一个指针
} Node;
typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_byte lsizenode; /* log2 of size of `node' array hash的大小(为2的整数次幂,这里表示的是幂次)*/
struct Table *metatable; /* 元表 */
TValue *array; /* array part 数组部分*/
Node *node; //hash表
Node *lastfree; /* any free position is before this position */
GCObject *gclist;
int sizearray; /* size of `array' array 数组的长度信息*/
} Table;
Commonheader如上一篇介绍,包含链表指针,数据类型,回收状态。Table分为连个部分,数组部分和key-value部分。数组部分不需要保存键值,这部分在底层实现,是使用者看来只是透明的。在往表中插入数值时,table会根据key-value当前的状态动态地使用这两个部分。数组部分保存的是索引 (0
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 key为数组情况 */
/* 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;
}
}
}
(1) 当查找的key是字符串时,具体有luaH_getstr函数执行。该函数首先计算字符串的hash值,然后将hash值通过lmod方法映射到hash表中的位置,再在该位置的node*链表中查找该字符串。找到返回TValue,未找到返回nil。
(2) 当查找的key为nil时,直接返回nil。
(3) 当查找的key为数字时,调用luaH_getint,该函数如下
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在(1,sizearray)之间,则直接返回array[key-1]。如果不在这个区间,则计算key的hash值,然后在对应的node*链中去查找。找到返回TValue,未找到返回nil。
(4) 对于其他的类型,都是先计算hash值,然后找到相应的node*位置,然后在该链中去查找。
3 table的赋值 luaH_set函数
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);
}
luaH_set首先查找,如果找到,直接返回key的指针。如果未找到,调用luaH_newkey函数,新建一个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.
** 新建一个KEY,值为nil
*/
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)首先判断key是不是空,为空的话出错
(2)调用mainposition找到主位置mp,主位置即key的hash映射到表中的位置
(3)如果主位置为空,那么直接将key放到主位置。如果主位置不为空,那么先通过getfreepos函数找到一个空闲的位置n(这个位置从末尾开始找)。如果找不到空闲位置,就要调用rehash重新调整hash表,调整好后调用luaH_set设置。如果找到空闲位置,计算mp的主位置othern。如果两个不相等,即mp的主位置不在mp指向的这里,那么mp移到n,并将n放在mp的主位置链上。如果两个相等,即mp的主位置就是这里,将key赋值给n,然后将n放到mp的主位置链上。
这里简单的模拟一下,素组代表hash表,采用取模方式获得hash位置,Last指向最后空余位置。
加入14,17。
加入27,发现主位置7不为空,判断17的主位置也在7,将27赋给位置0,将17的next指向0。
加入30,返现0位置不为空,同时27的主位置不在0,将27移到最后的lastfree,将30赋给0位置。
加入15
加入25,发现主位置5不为空,15的主位置也在5,将25赋给lastfree,将15next指向位置8。