1、概述
table是lua中唯一的表示数据结构的工具。它可以用于实现数据容器、函数环境(Env)、元表(metatable)、模块(module)和注册表(registery)等其他各种用途。因此了解table的实现是非常有必要的,根据《Lua中数据类型的源码实现》中知道,在Lua中,table是由结构体体Table来实现的。下面将以Lua 5.2.1的源码来看table的实现。
2、实现原理
在Lua5.0以后,table是以一种混合型数据结构来实现的,它包含一个哈希表部分和一个数组部分,比如下面操作的构造table:
成员TValue *array就是Table的数组部分,TValue表示Lua数据类型通用实现,再前面文章已经分析过。成员int sizearray指明了这个数组的大小。
成员Node *node就是Table的哈希表部分,其大小保存在成员lu_byte lsizenode中,注意保存的是哈希表大小的幂次,而不是实质大小。比如哈希表的大小为2^n,则lsizenode中保存的值是n,同时也说明哈希表的长度只能是2的幂次。
结构体Node中包含两个成员i_key和i_val,很显然分别表示key、value,其中value的数据类型就是通用的Lua数据类型TValue;key的数据类型是联合体,除了通常存储数据外,key还有一个作用是保存Node中next指针,也就是说key除了能保存TValue的数据结构外,还多了一个next指针,这个next指针就是用作同一个hash值下冲突时的链表指针。成员Node *lastfree就是链表的最后一个空元素。
成员struct Table *metatable是元表的指针,每个table的元表也是一个table。lu_byte flags用于元表元方法的一些优化手段,一共有8位用于标记是否没有某个元方法,初始值都是有的。成员GCObject *gclist用于GC。下面是创建一个表的接口,代码如下(ltable.c):
table是lua中唯一的表示数据结构的工具。它可以用于实现数据容器、函数环境(Env)、元表(metatable)、模块(module)和注册表(registery)等其他各种用途。因此了解table的实现是非常有必要的,根据《Lua中数据类型的源码实现》中知道,在Lua中,table是由结构体体Table来实现的。下面将以Lua 5.2.1的源码来看table的实现。
2、实现原理
在Lua5.0以后,table是以一种混合型数据结构来实现的,它包含一个哈希表部分和一个数组部分,比如下面操作的构造table:
local t = {100}
t[2] = 200
table.insert(t, 300)
t.x = 9.2
print(t[1], t[2], t[3], t.x)
其可能的实现方式如下图:
注意数组部分不需要保存键值,只要在底层实现时才考虑这种区别,其他情况,即使对虚拟机来说,访问表项也是由底层自动统一的,因为使用的时候无须考虑这种差别。在往表中插入数值时,表会根据key-value的值和表当前的数据内容自动动态地使用这个两个部分:数据部分试图保存所有key值介于1和某个上限n之间的值,非整数key和超过数据范围n的整数key对应的值将存入哈希表部分。
对于数组部分,要求的数组的大小同时满足:1到n之间至少一半的空间被利用(避免像稀疏数组一样浪费空间);并且n/2+1到n之间的空间至少有一个空间被利用(避免n/2个空间就能容纳所有数据时申请了n个空间浪费)。
对于哈希部分,解决冲突的方式是用开放寻址法(open addressing),即所有的元素都存在哈希表中,使用这种方法往往可以让Hash表内数据更紧凑,有更高效的空间利用率,并且在用这个方法时还做了改进,下面将通过源代码具体分析。
3、源码实现
首先来看对应的数据结构Table,其源码如下(lobject.h):
105 #define TValuefields Value value_; int tt_
544 /*
545 ** Tables
546 */
547
548 typedef union TKey {
549 struct {
550 TValuefields;
551 struct Node *next; /* for chaining */
552 } nk;
553 TValue tvk;
554 } TKey;
555
556
557 typedef struct Node {
558 TValue i_val;
559 TKey i_key;
560 } Node;
561
562
563 typedef struct Table {
564 CommonHeader;
565 lu_byte flags; /* 1<<p means tagmethod(p) is not present */
566 lu_byte lsizenode; /* log2 of size of `node' array */
567 struct Table *metatable;
568 TValue *array; /* array part */
569 Node *node;
570 Node *lastfree; /* any free position is before this position */
571 GCObject *gclist;
572 int sizearray; /* size of `array' array */
573 } Table;
Table结构的头CommonHeader与TString中是一样的,用于GC,实质上所有GC类型的头上相同的,都包含这个宏。
成员TValue *array就是Table的数组部分,TValue表示Lua数据类型通用实现,再前面文章已经分析过。成员int sizearray指明了这个数组的大小。
成员Node *node就是Table的哈希表部分,其大小保存在成员lu_byte lsizenode中,注意保存的是哈希表大小的幂次,而不是实质大小。比如哈希表的大小为2^n,则lsizenode中保存的值是n,同时也说明哈希表的长度只能是2的幂次。
结构体Node中包含两个成员i_key和i_val,很显然分别表示key、value,其中value的数据类型就是通用的Lua数据类型TValue;key的数据类型是联合体,除了通常存储数据外,key还有一个作用是保存Node中next指针,也就是说key除了能保存TValue的数据结构外,还多了一个next指针,这个next指针就是用作同一个hash值下冲突时的链表指针。成员Node *lastfree就是链表的最后一个空元素。
成员struct Table *metatable是元表的指针,每个table的元表也是一个table。lu_byte flags用于元表元方法的一些优化手段,一共有8位用于标记是否没有某个元方法,初始值都是有的。成员GCObject *gclist用于GC。下面是创建一个表的接口,代码如下(ltable.c):
368 Table *luaH_new (lua_State *L) {
369 Table *t = &luaC_newobj(L, LUA_TTABLE, sizeof(Table), NULL, 0)->h;
370 t->metatable = NULL;
371 t->flags = cast_byte(~0);
372 t->arr