一、table结构
1、Table结构体
首先了解一下table结构的组成结构,table是存放在GCObject里的。结构如下:
-
-
-
-
-
-
-
-
-
-
-
从table的结构可以看出,table在设计的时候以两种结构来存放数据。一般情况对于整数key,会用array来存放,而其它数据类型key会存放在哈希表上。并且用lsizenode作为链表的长度,sizearray作为数组长度。
2、Node结构体
-
-
-
-
-
-
-
-
-
-
-
-
-
Node结构很好理解,就是一个键值对的结构。主要是TKey结构,这里用了union,所以TKey的大小是nk的大小。并且实际上TValue与TValuefields是同一个结构,因此tvk与nk的TValuefields都是代表键值。而且这里有一个链表结构struct Node *next
,用于指向下一个有冲突的node。
二、创建table
table的创建通过lua_newtable
函数实现。通过定位具体实现是在luaH_new这个函数进行table的创建。代码如下:
-
Table *luaH_new (lua_State *L, int narray, int nhash) {
-
Table *t = luaM_new(L, Table);
-
luaC_link(L, obj2gco(t), LUA_TTABLE);
-
-
t->flags = cast_byte(~0);
-
-
-
-
-
t->node = cast(Node *, dummynode);
-
setarrayvector(L, t, narray);
-
setnodevector(L, t, nhash);
-
-
主要是对table进行初始化,其中setarrayvector是对数组大小进行设置,setnodevector是对hash表大小进行设置,具体代码如下:
-
-
-
-
static void setarrayvector (lua_State *L, Table *t, int size) {
-
-
-
luaM_reallocvector(L, t->array, t->sizearray, size, TValue);
-
-
for (i=t->sizearray; i<size; i++)
-
setnilvalue(&t->array[i]);
-
-
-
-
-
-
-
static void setnodevector (lua_State *L, Table *t, int size) {
-
-
-
t->node = cast(Node *, dummynode);
-
-
-
-
-
-
-
-
luaG_runerror(L, "table overflow");
-
-
-
-
t->node = luaM_newvector(L, size, Node);
-
-
-
-
-
-
-
-
-
t->lsizenode = cast_byte(lsize);
-
t->lastfree = gnode(t, size);
-
三、插入键值
键值的插入流程:
如图所示,已经大致可以清楚新键产生的过程。接下来会分析比较重要的几个模块。
1、table空间的动态扩展
无论是array还是hash表,都是以2的倍数进行扩展的。比较有区别的是,array数组sizearray记录的是真实大小,而hash表的lsizenode记录的是2的倍数。当hash表空间满的时候,才会重新分配array和hash表。比较重要的两个函数是rehash和resize,前一个是重新算出要分配的空间,后一个是创建空间。先分析下rehash函数:
-
-
static void rehash (lua_State *L, Table *t, const TValue *ek) {
-
-
-
-
-
for (i=0; i<=MAXBITS; i++) nums[i] = 0;
-
nasize = numusearray(t, nums);
-
-
totaluse += numusehash(t, nums, &nasize);
-
-
nasize += countint(ek, nums);
-
-
-
-
na = computesizes(nums, &nasize);
-
-
resize(L, t, nasize, totaluse - na);
-
-
-
-
-
-
static void resize (lua_State *L, Table *t, int nasize, int nhsize) {
-
-
int oldasize = t->sizearray;
-
int oldhsize = t->lsizenode;
-
-
-
setarrayvector(L, t, nasize);
-
-
setnodevector(L, t, nhsize);
-
-
-
-
for (i=nasize; i<oldasize; i++) {
-
if (!ttisnil(&t->array[i]))
-
setobjt2t(L, luaH_setnum(L, t, i+1), &t->array[i]);
-
-
-
luaM_reallocvector(L, t->array, oldasize, nasize, TValue);
-
-
-
for (i = twoto(oldhsize) - 1; i >= 0; i--) {
-
-
-
setobjt2t(L, luaH_set(L, t, key2tval(old)), gval(old));
-
-
-
-
luaM_freearray(L, nold, twoto(oldhsize), Node);
-
2、键的创建规则
2、1整数类型
一般情况下,整数类型的键都是放在数组里的,但是有2种特殊情况会被分配到hash表里。
对于存放在数组有一个规则,每插入一个整数key时,都要判断包含当前key的区间[1, 2^n]里,是否满足table里所有整数类型key的数量大于2^(n - 1),如果不成立则需要把这个key放在hash表里。这样设计,可以减少空间上的浪费,并可以进行空间的动态扩展。例如:
a[0] = 1, a[1] = 1, a[5]= 1
结果分析:数组大小4, hash大小1,a[5]本来是在8这个区间里的,但是有用个数3 < 8 / 2,所以a[5]放在了hash表里。
a[0] = 1, a[1] = 1, a[5] = 1, a[6] = 1,
结果分析:数组大小4,hash大小2,有用个数4 < 8 / 2,所以a[5],a[6]放在hash表里。
a[0] = 1, a[1] = 1, a[5] = 1, a[6] = 1, a[7] = 1
结果分析:数组大小8,hash大小0, 有用个数5 > 8 / 2。
数组大小的规定由以下函数实现:
-
static int computesizes (int nums[], int *narray) {
-
-
-
-
-
-
for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) {
-
-
-
-
-
-
-
-
-
-
-
lua_assert(*narray/2 <= na && na <= *narray);
-
-
通过前面的分析,可以清楚的知道这个函数的意图,根据统计出来的所有整数键重新划分数据大小。其中参数nums[]是一个数组,每个nums[i]都记录了在数组中[2^i, 2^(i + 1)]的区间内不为空的数量。参数narray是个指针,获得重新调整后的数组大小。
另外还有一种被分配到hash表里的情况,当hash表有空间并且当前key值越界的时候,会先放在hash表里,直到hash表满的时候,才会把hash表里的所有整数键按上面的方法进行操作。
2、2其他类型
对于非整数类型的键会被全部分配到hash表里。前面我们提到,table用一个Node数组来作为hash表的容器,利用hash算法把键转化为某个数组下标,来进行存放。hash表在设计的时候有一点比较巧妙的地方,我们知道hash算出来的位置有可能是会冲突的,所以如果当前插入的key发生冲突的时候,会把当前插入的key放到lastfree中,并把当前的node链接到冲突链中。这里有一种情况,如果插入的newkey的位置不是因为冲突而被占,而是其他oldkey因为冲突暂时存放的话,会把这个位置让会给原本属于这个位置的newkey,并把oldkey放到lastfree中。可能不好理解,还是来模拟解释下:

位置被占的情况:

接下来再来看下主要代码就比较容易理解了:
-
static TValue *newkey (lua_State *L, Table *t, const TValue *key) {
-
Node *mp = mainposition(t, key);
-
-
if (!ttisnil(gval(mp)) || mp == dummynode) {
-
-
-
-
-
return luaH_set(L, t, key);
-
-
lua_assert(n != dummynode);
-
othern = mainposition(t, key2tval(mp));
-
-
-
-
-
-
-
while (gnext(othern) != mp) othern = gnext(othern);
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
gkey(mp)->value = key->value; gkey(mp)->tt = key->tt;
-
luaC_barriert(L, t, key);
-
lua_assert(ttisnil(gval(mp)));
-
-
3、键值的赋值
键值对赋值的过程,就是通过获取栈顶的前两个位置作为key和value,如果这个key在table里是不存在的则创建新的key,并返回key对应的TValue指针,再对指针其进行赋值。具体实现如下:
-
-
-
-
void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
-
-
-
-
for (loop = 0; loop < MAXTAGLOOP; loop++) {
-
-
-
-
-
TValue *oldval = luaH_set(L, h, key);
-
-
-
-
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) {
-
-
setobj2t(L, oldval, val);
-
-
luaC_barriert(L, h, val);
-
-
-
-
-
-
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_NEWINDEX)))
-
luaG_typeerror(L, t, "index");
-
-
-
callTM(L, tm, t, key, val);
-
-
-
-
-
-
-
luaG_runerror(L, "loop in settable");
-
四、for循环的分析
1、for循环的入栈操作
在lua里,table.foreach(key, value)可以遍历整一个table,因此来对foreach具体分析一下。foreach函数的作用,是从table里循环查找下一个key和value,压入栈顶,并与lua层定义的function一起进行处理的一个过程。具体代码如下:
-
static int foreach (lua_State *L) {
-
luaL_checktype(L, 1, LUA_TTABLE);
-
luaL_checktype(L, 2, LUA_TFUNCTION);
-
-
-
-
-
-
-
-
-
-
-
-
-
栈的活动模型流程如图:
->
->
->
->
->
->
2、键值的查找
键值的查找必定是先从数组开始找,数组找完了之后才按hash表找。并且都是以下标从小到大的顺序遍历,每次找到之后,都会把key存放起来,并把对应的value压栈。下一次循环时在算出当前key的位置下标,通过位置下标往后移一单位来获得下一个key。流程如图:

具体的代码如下:
-
int luaH_next (lua_State *L, Table *t, StkId key) {
-
-
-
int i = findindex(L, t, key);
-
-
for (i++; i < t->sizearray; i++) {
-
if (!ttisnil(&t->array[i])) {
-
setnvalue(key, cast_num(i+1));
-
-
-
setobj2s(L, key+1, &t->array[i]);
-
-
-
-
-
for (i -= t->sizearray; i < sizenode(t); i++) {
-
if (!ttisnil(gval(gnode(t, i)))) {
-
setobj2s(L, key, key2tval(gnode(t, i)));
-
setobj2s(L, key+1, gval(gnode(t, i)));
-
-
-
-
-
-
lua数据结构之table的内部实现
原文:https://www.cnblogs.com/gangtie/p/12712970.html