1.table的特性
- 在Lua中table是个非常重要的类型,通过使用table的一些特性可以实现许多数据结构,例如map,array queue,stack等。
- 通过使用者角度来讲,table既可以当作array使用也可以当作map使用,那么对于设计者来讲,那么需要保证table的高效率的查找、插入、遍历。
- 当然,table的设计者还提出了metatable(元表)的概念,以供使用者可以用来实现继承、操作符重载等设计,不过metatable暂时不在这边文章进行讨论。
2.table的定义
typedef union TKey {
struct {
TValuefields;
int next; /* 用于标记链表下一个节点 */
} 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 */
unsigned int sizearray; /* size of 'array' array */
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
struct Table *metatable;
GCObject *gclist;
} Table;
- flags : 元方法的标记,用于查询table是否包含某个类别的元方法
- lsizenode : (1<<lsizenode)表示table的hash部分大小
- sizearray : table的数组部分大小
- array : table的array数组首节点
- node :table的hash表首节点
- lastfree : 表示table的hash表空闲节点的游标
- metatable : 元表
- gclist : table gc相关的参数
为了提高table的插入查找效率,在table的设计上,采用了array数组和hashtable(哈希表)两种数据的结合。
所以table会将部分整形key作为下标放在数组中, 其余的整形key和其他类型的key都放在hash表中。
3.hash表结构
在table中的实现中,hash表占绝大部分比重,下面是table中hash表的结构示意简图:
hash表在解决冲突有两个常用的方法:
- 开放定址法:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的 地址则表明表中无待查的关键字,即查找失败。
- 链地址法:又叫拉链法,所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0…m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1。
简单对比可以发现以上两种方法的优缺点:
开放定址法相比链地址法节省更多的内存,但在插入和查找的时候拥有更高的复杂度。
但是table中的hash表的实现结合了以上两种方法的一些特性:
- 查找和插入等同链地址法复杂度。
- 内存开销近似等同于开放定址法。
原因是table中的hash表虽然采用链地址法的形式处理冲突,但是链表中的额外的节点是hash表中的节点,并不需要额外开辟链表节点;下面是TKey结构的介绍:
那么如何将hash表的空余节点利用起来作为链表的节点呢?这个算法的实现得益于lastfree这个指针的作用,后面会详细介绍。
4.table的创建
lua中通过luaH_new来创建一个新table:
Table *luaH_new (lua_State *L) {
GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table));
Table *t = gco2t(o);
t->metatable = NULL;
t->flags = cast_byte(~0);
t->array = NULL;
t->sizearray = 0;
setnodevector(L, t, 0);
return t;
}
此时,table中的array部分和hash部分都为空。
5.luaH_get分析
table中通过这个函数来从表中查找key对应的值;可以看到它会通过key的类型不同,从而进行不同的处理。
const TValue *luaH_get (Table *t, const TValue *key) {
switch (ttype(key)) {
case LUA_TSHRSTR: return luaH_getshortstr(t, tsvalue(key));
case LUA_TNUMINT: return luaH_getint