Lua table源码分析
来源:互联网 发布:淘宝一键复制软件 编辑:程序博客网 时间:2024/06/05 16:27
一、table结构
1、Table结构体
首先了解一下table结构的组成结构,table是存放在GCObject里的。结构如下:
typedefstruct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_bytelsizenode; /*以2的lsizenode次方作为哈希表长度 */
struct Table*metatable/*元表 */;
TValue *array; /*数组 */
Node *node; /*哈希表 */
Node *lastfree; /*指向最后一个为闲置的链表空间 */
GCObject *gclist; ????这个是做什么用的
intsizearray; /*数组的大小 */
} Table;
从table的结构可以看出,table在设计的时候以两种结构来存放数据。一般情况对于整数key,会用array来存放,而其它数据类型key会存放在哈希表上。并且用lsizenode作为链表的长度,sizearray作为数组长度。
table 分为三块内存,分别是table结构,数组,hash数组。hash数组大小为 2 的n次幂,所以最小是1.lua进行了优化定义了一个空的hash表dummynode,所有的空的table表的hash node指针都指向这个dummynode表。dummynode是只读的所以没有线程安全问题
#define dummynode (&dummynode_)
static const Node dummynode_ = {
{NILCONSTANT}, /* value */
{{NILCONSTANT, 0}} /* key */
};
2、Node结构体
typedefunion
TKey {
struct
{
TValuefields;
struct
Node *next;
/* 指向下一个冲突node */
} nk;
TValue tvk;
} TKey;
// TKey
保存了
key
的值和指向冲突时下一个节点的指针
typedefstruct
Node {
TValue i_val;
TKey i_key;
} 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) {
GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table)); //
创建一个
table
对象
并加入全局
gclist
中
Table *t = gco2t(o);
t->metatable = NULL; //
设置元表为
nil
t->flags = cast_byte(~0);
t->array = NULL; //
设置数组部分为
nil
t->sizearray = 0;
setnodevector(L, t, 0); //
设置
hash
表部分
return t;
}
主要是对table进行初始化, setnodevector是对hash表大小进行设置,具体代码如下:
static void setnodevector (lua_State *L, Table *t, unsigned int size)
{
if (size == 0) //hash 部分为nil 则hash指针指向那个常量的空表
{ /* no elements to hash part? */
t->node = cast(Node *, dummynode); /* use common 'dummynode' */
t->lsizenode = 0;
t->lastfree = NULL; /* signal that it is using dummy node */
}
else {
int i;
int lsize = luaO_ceillog2(size); //计算ceil(log2(size))函数用数组模拟log2()的实现
if (lsize > MAXHBITS)
luaG_runerror(L, "table overflow");
size = twoto(lsize); //分配的hash大小一定是2的整数幂求实际应该分配的大小
t->node = luaM_newvector(L, size, Node); //分配hash表空间
for (i = 0; i < (int)size; i++) {//遍历hash表将每个节点的keyvalue赋值为nil
Node *n = gnode(t, i);
gnext(n) = 0;
setnilvalue(wgkey(n));
setnilvalue(gval(n));
}
t->lsizenode = cast_byte(lsize);
t->lastfree = gnode(t, size); //记录最后一个空闲位置的索引
}
}
判断hash部分是不是空,是指向常量节点,计算实际分配的hash表空间大小,分陪内存,初始化key和value为nil,保存hash表大小,和最后的空闲节点索引。
三、释放table
void luaH_free (lua_State *L, Table *t) {
if (!isdummy(t))
luaM_freearray(L, t->node, cast(size_t, sizenode(t)));
luaM_freearray(L, t->array, t->sizearray);
luaM_free(L, t);
}
这里就是调用Lua内存管理函数,释放table结构,数组,hash表这三块内存。
四、重置table数组和hash表大小
static void rehash (lua_State *L, Table *t, const TValue *ek) {
unsigned int asize;
unsigned int na;
unsigned int nums[MAXABITS + 1]; //每个区间整数key的个数
int i;
int totaluse; //hash + array 元素个数的
for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */
na = numusearray(t, nums); /* 计算数组部分每个区间key个数和数组总元素个数*/
totaluse = na; /* all those keys are integer keys */
totaluse += numusehash(t, nums, &na); /*计算hash部分每个区间key个数和数组总元素个数*/
/* count extra key */
na += countint(ek, nums); //计算新加入的key
totaluse++;
/* compute new size for array part */
asize = computesizes(nums, &na); //计算数组部分应该分配多少空间asize 总大小na 已有元素大小
/* resize the table to new computed sizes */
luaH_resize(L, t, asize, totaluse - na); //重新分配空间
}
rehash 主要是统计当前有多少个有效的键值,并计算出数组部分需要开辟多大的空间。要保证数组空间的利用率为50%以上。再调用luaH_resize重新分配table的数组和hash表的大小。
void luaH_resize (lua_State *L, Table *t, unsigned int nasize,
unsigned int nhsize)
{
unsigned int i;
int j;
unsigned int oldasize = t->sizearray; //保存old数组size
int oldhsize = allocsizenode(t); //保存old hash表 size
Node *nold = t->node; /* 保存老的hash表地址 */
if (nasize > oldasize) /* 是不是数组扩大了 */
setarrayvector(L, t, nasize); //分配新的数组并清空超过的部分
/* 创建新的hash表*/
setnodevector(L, t, nhsize);
if (nasize < oldasize) { /* 数组部分缩减了吗*/
t->sizearray = nasize;
/* re-insert elements from vanishing slice */
for (i=nasize; i<oldasize; i++) { //将超出的部分保存在hash表中
if (!ttisnil(&t->array[i]))
luaH_setint(L, t, i + 1, &t->array[i]);
}
/* 分配新的数组*/
luaM_reallocvector(L, t->array, oldasize, nasize, TValue);
}
/* 从后向前遍历将老的值放到新hash表里 */
for (j = oldhsize - 1; j >= 0; j--) {
Node *old = nold + j;
if (!ttisnil(gval(old))) {
/* doesn't need barrier/invalidate cache, as entry was
already present in the table */
setobjt2t(L, luaH_set(L, t, gkey(old)), gval(old));
}
}
if (oldhsize > 0) /* not the dummy node? */
luaM_freearray(L, nold, cast(size_t, oldhsize)); /* free old hash */
}
数组扩容,重新非配数组空间,并清空超出部分。数组缩减,将超出部分的内容,插入到hash表中。
五、table插入
通过luaH_new创建的Table开始实际上是一张空表,只是包含了Table本身的数据结构。创建完以后需要添加元素,
添加到函数为:luaH_set,该函数在设置前会根据key的值去获取对应的Value,如果能找到,则直接设置,如果找不到,则需要申请一个位置来存放(Key,Value)。luaH_set 返回key所对应值的地址,然后通过调用setobjt2t 设置键所对应的值
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);
}
六、创建table hash表键
TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
Node *mp;
TValue aux;
//如果key是float值并且可以装换为int 则将其转换为int
if (ttisnil(key)) luaG_runerror(L, "table index is nil");
else if (ttisfloat(key)) {
lua_Integer k;
if (luaV_tointeger(key, &k, 0)) { /* does index fit in an integer? */
setivalue(&aux, k);
key = &aux; /* insert it as an integer */
}
else if (luai_numisnan(fltvalue(key)))
luaG_runerror(L, "table index is NaN");
}
mp = mainposition(t, key); //获取key 在hash表中的主位置
if (!ttisnil(gval(mp)) || isdummy(t)) { //主位置已经存在元素或者是空hash表
Node *othern;
Node *f = getfreepos(t); /* 获取空闲位置 */
if (f == NULL) { /* 没有空位置 */
rehash(L, t, key); /* 重构hash表 */
/* whatever called 'newkey' takes care of TM cache */
return luaH_set(L, t, key); /* insert key into grown table */
}
lua_assert(!isdummy(t));
othern = mainposition(t, gkey(mp)); //获取这个位置上key的主位置(注意这里存放的元素主位置不一定就在这,有可能是冲突后放置在这个位置的元素)
if (othern != mp) { /* 这个位置的元素主位置不在这里也就是当前节点放置的是冲突节点*/
/* other 是当前主位置上的冲突节点应当放置的位置 map是冲突节点当前放置的位置
这里遍历查找map位置前一个冲突节点的位置 */
while (othern + gnext(othern) != mp) /* */
othern += gnext(othern);
//移动mp位置上的元素将新key放置在这个位置上
gnext(othern) = cast_int(f - othern); /* */
*f = *mp; /* */
if (gnext(mp) != 0) {
gnext(f) += cast_int(mp - f); /* correct 'next' */
gnext(mp) = 0; /* now 'mp' is free */
}
setnilvalue(gval(mp));
}
else { //当前节点的主位置就在这里那么将新key放在freepos 关联相关next
if (gnext(mp) != 0)
gnext(f) = cast_int((mp + gnext(mp)) - f);
else
lua_assert(gnext(f) == 0);
gnext(mp) = cast_int(f - mp);
mp = f;
}
}
//主位置没有元素那么直接赋值
setnodekey(L, &mp->i_key, key);
luaC_barrierback(L, t, key);
lua_assert(ttisnil(gval(mp)));
return gval(mp); //返回 node 的value 地址
}
键值的插入流程:
(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。
七、table 迭代(lua_nextfindindex)
int luaH_next (lua_State *L, Table *t, StkId key) {
unsigned int i = findindex(L, t, key); /*找到key在数组的索引如果key在hash表中那么返回在hash表中的索引 + sizearray */
for (; i < t->sizearray; i++) { /* try first array part */
if (!ttisnil(&t->array[i])) { /* a non-nil value? */
//key 是栈顶位置这里将下一个索引放入栈栈并没有增加只是覆盖了上一个key的值
setivalue(key, i + 1);
//将value 压入栈栈会在这个函数的外部增长
setobj2s(L, key+1, &t->array[i]);
return 1;
}
}
//注意这里当I == sizearray 是证明数组遍历结束从0开始遍历hash部分压入hash第一个元素的key 和第一个元素的值
for (i -= t->sizearray; cast_int(i) < sizenode(t); i++) { /* hash part */
if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */
setobj2s(L, key, gkey(gnode(t, i)));
setobj2s(L, key+1, gval(gnode(t, i)));
return 1;
}
}
return 0; /* 返回0没有元素了 */
}
static unsigned int findindex (lua_State *L, Table *t, StkId key) {
unsigned int i;
if (ttisnil(key)) return 0; /* 如果key为空则返回第一个0 代表开始迭代*/
i = arrayindex(key);
if (i != 0 && i <= t->sizearray) /* 查找数组部分 */
return i; /* yes; that's the index */
else {
int nx;
Node *n = mainposition(t, key); //计算key的主位置索引
for (;;) { /* 从主位置开始依次遍历和其冲突的节点 */
/* 比较key的值是否相等 */
if (luaV_rawequalobj(gkey(n), key) ||
(ttisdeadkey(gkey(n)) && iscollectable(key) &&
deadvalue(gkey(n)) == gcvalue(key))) {
i = cast_int(n - gnode(t, 0)); /* key index in hash table */
/*相等返回key的位置索引*/
return (i + 1) + t->sizearray;
}
nx = gnext(n);
if (nx == 0)
luaG_runerror(L, "invalid key to 'next'"); /* key not found */
else
n += nx; //查找下一个冲突节点
}
}
}
static unsigned int arrayindex (const TValue *key) {
if (ttisinteger(key)) {
lua_Integer k = ivalue(key);
if (0 < k && (lua_Unsigned)k <= MAXASIZE)
return cast(unsigned int, k);
}
return 0; /* 'key' did not match some condition */
}
luaH_next 是内部api 外部调用接口lua_next lua_next 实现如下
LUA_API int lua_next (lua_State *L, int idx) { //idx为table在栈中的索引
StkId t;
int more;
lua_lock(L);
t = index2adr(L, idx); //获取table指针
api_check(L, ttistable(t));
more = luaH_next(L, hvalue(t), L->top - 1); //从栈中取出一个key 压入下一个key 和value 遍历完返回0
if (more) {
api_incr_top(L); //栈top++
}
else /* no more elements */
L->top -= 1; /* remove key */ 遍历完移除最后一个key
lua_unlock(L);
return more;
}
C 遍历 table 代码:
// 进行下面步骤前先将 table 压入栈顶
int nIndex = lua_gettop( pLua ); // 取 table 索引值
lua_pushnil( pLua ); // nil 入栈作为初始 key
while( 0 != lua_next( pLua, nIndex ) )
{
// 现在栈顶(-1)是 value,-2 位置是对应的 key
// 这里可以判断 key 是什么并且对 value 进行各种处理
lua_pop( pLua, 1 ); // 弹出 value,让 key 留在栈顶
}
// 现在栈顶是 table
lua_next() 这个函数的工作过程是:
1) 先从栈顶弹出一个 key
2) 从栈指定位置的table里取下一对key-value,先将 key入栈再将 value 入栈
3) 如果第 2 步成功则返回非 0 值,否则返回 0,并且不向栈中压入任何值
第 2 步中从 table里取出所谓“下一对 key-value”是相对于第 1步中弹出的 key的。table里第一对 key-value的前面没有数据,所以先用 lua_pushnil()压入一个 nil 充当初始 key。
- Lua table源码分析
- LUA源码分析三:table分析(1)
- LUA源码分析三:table分析(1)
- Lua中table类型源码分析
- lua table源码学习
- Lua 5.3 源码分析(六) 字符串 Table
- Lua源码分析(1)
- Lua 源码分析(一)
- lua源码分析文档
- lua源码分析
- Lua 源码分析 TString
- Lua中table类型的源码实现
- Lua中table类型的源码实现
- Lua 简单Lua解释器源码分析
- lua 中table的使用和分析
- lua之table数据结构分析(一)
- Lua源码分析(1) -- 简介
- Lua源码分析(3) -- 虚拟机
- input_radio_自定义样式
- Linux+Anaconda+ tensorflow + keras 安装+换源
- 一篇关于了解generatorConfig自动生成model,dao包的心路历程
- Vue-Router(三) 编程式导航
- eclipse中添加额外的jar包
- Lua table源码分析
- Python爬虫入门-利用scrapy爬取淘女郎照片
- 构建乘积数组
- 外设位宽为8、16、32时,CPU与外设之间地址线的连接方法
- <C/C++>日期和时间的使用(time相关函数大全)
- LeetCode 414. Third Maximum Number
- 快速解决java端口占用
- 加密算法和文件格式RSA、X509、PKCSXX
- Hive安装与配置