Lua的数据结构
1.Table的几个小知识点
- table使用关联数组,我们可以使用任意类型的值来作为数组的索引(不能使用nil)
- table没有固定的大小,可以动态的添加元素
2.table的构造
ab都会指向同一个内存块,如果a设置为nil,b依旧能访问该内存块的元素,直到b设置为nil后,Lua的垃圾回收机制会清理相应的内存。所以当b在更改table内的值后,a再去访问的时候,值也是改变的。
3. 从table结构体分析table属性
1 typedef struct Table { 2 CommonHeader; 3 lu_byte flags; /* 1<<p means tagmethod(p) is not present */ 4 lu_byte lsizenode; /* 以2的lsizenode次方作为哈希表长度 */ 5 struct Table *metatable /* 元表 */; 6 TValue *array; /* 数组 */ 7 Node *node; /* 哈希表 */ 8 Node *lastfree; /* 指向最后一个为闲置的链表空间 */ 9 GCObject *gclist; 10 int sizearray; /* 数组的大小 */ 11 } Table;
- CommonHeader 为所有可回收资源提供标记头
- lu_byte其实是 typedef unsigned char lu_byte,lu_byte flags用于表示表中提供了哪些元方法(元方法是什么?我们下面和元表一起解释)
- lu_byte lsizenode 它的值就是表的长度,但是散列表大小的扩增一定是2的幂,如果散列桶数组要扩展的话,也是每次在原大小的基础上乘以2的形式扩展。
- struct Table *metatable 也就是元表。
- TValue *array是指向数组的指针
- Node *node 是指向该表的散列桶数组起始位置的指针
1 typedef struct lua_TValue { 2 TValuefields; 3 } TValue; 4 5 TValuefields: 6 #define TValuefields Value value; int tt 7 8 Value: 9 typedef union { 10 GCObject *gc; 11 void *p; 12 lua_Number n; 13 int b; 14 } Value;
3.2 Node类型
1 typedef union TKey { 2 struct { 3 TValuefields; 4 struct Node *next; /* for chaining */ 5 } nk; 6 TValue tvk; 7 } TKey; 8 9 typedef struct Node { 10 TValue i_val; 11 TKey i_key; 12 } Node;
每个Node都是一个键值对,里面包含了key和value。tvk是key的值,但是当我们出现hash冲突,此时lua的hash算法比较特殊,一般情况下,我们的hash算法都是根据key算出hash,然后如果有冲突的话,就放在改位置的链表上。而lua不同,当它出现hash冲突的时候,会在hash表中找到一个空的位置x,来存放key,并且让冲突处的节点的nk.next指向x。
这意味着什么呢?发生冲突我们无需重新分配空间来存储冲突的key,而是利用hash表上未用过的空白处来存储。刚才我们将key放在了空位置x,如果此时存在另一个key2,它算出的hash值是空位置x,而我们刚才的key只是因为hash冲突了,占用了其他key的位置,这个时候我们就讲key2放在x上,将key再放到另一个空白处。
3.3 table的实现
lua的table其实由数组段和hash两部分组成,当你的key值不会过于离散的时候,lua就会将它存储在数组段(也就是下图的array),反正会存储在hash段(也就是下图的node),这个分割线是以数组段的利用率不低于50%为准。hash段采用闭散列的算法,它将有冲突的key存储在空闲槽中,而不额外分配内存。
在我们table结构体中,array和sizearray都是表示数组段。
而lsizenode和node,lastfree是表示hash段。node指向hash表的起始位置,lsizenode是log2(node指向的哈希表的节点数目),lastfree指向node里面最后一个未使用的节点(因为我们在hash冲突的时候,是从后往前查找未使用的节点,lastfree存储最后一个未使用节点就可以方便查找)
如果此时hash表中已经没有空格了,那么lua就会resize这个hash表(等会再谈lua的动态扩增)
4. table的创建、查找、动态分配
4.1 table的创建
1 Table *luaH_new (lua_State *L, int narray, int nhash) { 2 Table *t = luaM_new(L, Table); 3 luaC_link(L, obj2gco(t), LUA_TTABLE); 4 t->metatable = NULL; 5 t->flags = cast_byte(~0); 6 /* temporary values (kept only if some malloc fails) */ 7 t->array = NULL; 8 t->sizearray = 0; 9 t->lsizenode = 0; 10 t->node = cast(Node *, dummynode); 11 setarrayvector(L, t, narray); 12 setnodevector(L, t, nhash); 13 return t; 14 }
4.2 table的查找
1 const TValue *luaH_get (Table *t, const TValue *key) { 2 switch (ttype(key)) { 3 case LUA_TNIL: return luaO_nilobject; 4 case LUA_TSTRING: return luaH_getstr(t, rawtsvalue(key)); 5 case LUA_TNUMBER: { 6 int k; 7 lua_Number n = nvalue(key); 8 lua_number2int(k, n); 9 if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */ 10 return luaH_getnum(t, k); /* use specialized version */ 11 /* else go through */ 12 } 13 default: { 14 Node *n = mainposition(t, key); 15 do { /* check whether `key' is somewhere in the chain */ 16 if (luaO_rawequalObj(key2tval(n), key)) 17 return gval(n); /* that's it */ 18 else n = gnext(n); 19 } while (n); 20 return luaO_nilobject; 21 } 22 } 23 }
因为key为数字比较特殊,所以研究一把luaH_getnum函数的实现
1 const TValue *luaH_getnum (Table *t, int key) { 2 /* (1 <= key && key <= t->sizearray) */ 3 if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray)) 4 return &t->array[key-1]; 5 else { 6 lua_Number nk = cast_num(key); 7 Node *n = hashnum(t, nk); 8 do { /* check whether `key' is somewhere in the chain */ 9 if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk)) 10 return gval(n); /* that's it */ 11 else n = gnext(n); 12 } while (n); 13 return luaO_nilobject; 14 } 15 }
如果key大于等于1,小于数组的长度,则从数组中取出对应的键值,否则利用hashnum找到key对应的node位置,遍历node链表,返回对应的值
4.3 table的赋值
1 static TValue *newkey (lua_State *L, Table *t, const TValue *key) { 2 Node *mp = mainposition(t, key); 3 if (!ttisnil(gval(mp)) || mp == dummynode) { 4 Node *othern; 5 Node *n = getfreepos(t); /* get a free place */ 6 if (n == NULL) { /* cannot find a free place? */ 7 rehash(L, t, key); /* grow table */ 8 return luaH_set(L, t, key); /* re-insert key into grown table */ 9 } 10 lua_assert(n != dummynode); 11 othern = mainposition(t, key2tval(mp)); 12 if (othern != mp) { /* is colliding node out of its main position? */ 13 /* yes; move colliding node into free position */ 14 while (gnext(othern) != mp) othern = gnext(othern); /* find previous */ 15 gnext(othern) = n; /* redo the chain with `n' in place of `mp' */ 16 *n = *mp; /* copy colliding node into free pos. (mp->next also goes) */ 17 gnext(mp) = NULL; /* now `mp' is free */ 18 setnilvalue(gval(mp)); 19 } 20 else { /* colliding node is in its own main position */ 21 /* new node will go into free position */ 22 gnext(n) = gnext(mp); /* chain new position */ 23 gnext(mp) = n; 24 mp = n; 25 } 26 } 27 gkey(mp)->value = key->value; gkey(mp)->tt = key->tt; 28 luaC_barriert(L, t, key); 29 lua_assert(ttisnil(gval(mp))); 30 return gval(mp); 31 }
往table中插入新值,先检测key的主位置(main position)是否为空,主位置就是key的哈希值在node中的位置。
如果主位置为空,就直接插入,主位置不为空,检查占领该位置的key的主位置是不是在这个地方,如果不在,则将该key移动到其他空闲位置,将要插入的key插入到这个位置中。如果在这个地方,则将要插入的key插入到一个空槽中。
如果找不到空闲位置放新键值,就rehash函数,扩增hash表的大小,再找出新位置,再调用luaH_set把要插入的key插入到新的哈希表中,直接返回LuaH_set的结果。
4.4 table的动态增长
4.4.1 rehash
1 //加入key,重新分配hash与array的空间 2 static void rehash (lua_State *L, Table *t, const TValue *ek) { 3 int nasize, na;//nasize前期累计整数key个数,后期做为数组空间大小,na表示数组不为nil的个数 4 int nums[MAXBITS+1]; /* 累计各个区间整数key不为nil的个数,包括hash, 例如nums[i]表示累计在[2^(i-1),2^i]区间内的整数key个数*/ 5 int i; 6 int totaluse;//记录所有已存在的键,包括hash和array,即table里的成员个数 7 for (i=0; i<=MAXBITS; i++) nums[i] = 0; /* 初始化所有计数区间*/ 8 nasize = numusearray(t, nums); /* 以区间统计数组里不为nil的个数,并获得总数*/ 9 totaluse = nasize; /* all those keys are integer keys */ 10 totaluse += numusehash(t, nums, &nasize); /* 统计hash表里已有的键,以及整数键的个数已经区间分布*/ 11 //如果新key是整数类型的情况 12 nasize += countint(ek, nums); 13 //累计新key 14 totaluse++; 15 /* 重新计算数组空间 */ 16 na = computesizes(nums, &nasize); 17 /* 重新创建内存空间, nasize为新数组大小 18 //totaluse - na表示所有键的个数减去新数组的个数,即为新hash表需要存放的个数 */ 19 resize(L, t, nasize, totaluse - na); 20 }
4.2 resize
1 static void resize (lua_State *L, Table *t, int nasize, int nhsize) { 2 int i; 3 int oldasize = t->sizearray; 4 int oldhsize = t->lsizenode; 5 Node *nold = t->node; /* 保存当前的hash表,用于后面创建新hash表时,可以重新对各个node赋值*/ 6 if (nasize > oldasize) /* 是否需要扩展数组 */ 7 setarrayvector(L, t, nasize); 8 /* 重新分配hash空间*/ 9 setnodevector(L, t, nhsize); 10 if (nasize < oldasize) { /* 小于之前大小,即有部分整数key放到了hash里 */ 11 t->sizearray = nasize; 12 /* 超出部分存放到hash表里*/ 13 for (i=nasize; i<oldasize; i++) { 14 if (!ttisnil(&t->array[i])) 15 setobjt2t(L, luaH_setnum(L, t, i+1), &t->array[i]); 16 } 17 /* 重新分配数组空间,去掉后面溢出部分*/ 18 luaM_reallocvector(L, t->array, oldasize, nasize, TValue); 19 } 20 /* 从后到前遍历,把老hash表的值搬到新表中*/ 21 for (i = twoto(oldhsize) - 1; i >= 0; i--) { 22 Node *old = nold+i; 23 if (!ttisnil(gval(old))) 24 setobjt2t(L, luaH_set(L, t, key2tval(old)), gval(old)); 25 } 26 //释放老hash表空间 27 if (nold != dummynode) 28 luaM_freearray(L, nold, twoto(oldhsize), Node); /* free old array */ 29 }
作者:凉拌姨妈好吃
链接:https://www.jianshu.com/p/56ca3d77c7de
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

浙公网安备 33010602011771号