Lua的表的定义:

typedef struct Table {
CommonHeader;
lu_byte flags;
lu_byte lsizenode; /* log2 of size of `node' array */
struct Table *metatable;
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
GCObject *gclist;
int sizearray; /* size of `array' array */
} Table;

这里将Table分为了两个部分:数组部分,array指针指向数组部分的首地址,sizearray是数组的尺寸,绝大部分(注意:不是全部)正整数为key的数据都存放在数组部分;node指针指向一个hash桶,对于不能存放在数组部分的数据,都存放在hash中.如下图所示:

hash部分需要特别注意的一点是:在物理上,所有hash部分的数据,其实是存放一块连续的内存中的,即node指针指向的数组;但是从逻辑上来看,如果几块数据在同一个hash桶上,那么又是通过next指针串联起来的.

以图中的示例来分析,node数组的第一个和第三个元素,在物理上是第一和第三个元素,但是在逻辑上,它们是通过next指针串联起来的.

Table查找数据

有了以上的了解,从Table中查找一个数据的伪代码就很显而易见了:

如果输入的Key是一个正整数,并且它的值 > 0 && <= 数组大小

尝试在数组部分查找

否则尝试在Hash部分进行查找:

计算出该Key的Hash值(ltable.c中的mainposition函数),根据此Hash值访问node数组得到Hash桶所在位置

遍历该Hash桶下的所有链表元素,直到找到该Key为止

以上已经明白了Table的大致结构,来看看Table中如果新加入新的数据会怎么处理.这里有一些内容,要留到后面讲解到Lua虚拟机的时候才触及,这里先讲解一下,当新插入数据时,Table内的数组和Hash部分,做了哪些变化.

这部分中,核心的算法在ltable.c的rehash函数中,这个函数是计算当新添加数据时,数组和hash重新分配之后各自的尺寸是多少,伪代码如下:

首先分配一个位图nums,将其中的所有位置0,这个位图的意义在于:nums数组中第i个元素存放的是key在2^(i-1), 2^i之间的元素数量

遍历lua Table中的数组部分,计算在数组部分中的元素数量,更新对应的nums数组元素数量.(numusearray函数)

遍历lua Table中的Hash部分,因为其中也可能存放了正整数,也根据这里的正整数数量更新对应的nums数组元素数量.(numusehash函数)

此时nums数组已经有了当前这个Table中所有正整数的分配统计,逐个遍历nums数组,如果当前已经有的根据新的数组大小和Hash大小重新分配table的大小(computesizes函数)

这里要特别讲解的是computesizes函数,在前面的两个函数调用numusearray函数和numusehash函数之后,此时在nums位图中,已经存放了所有有关整数key的信息,即在[2^(i-1), 2^i]范围内,有多少数据.前面曾经提到过,并不是所有的正整数,都会存放在数组部分的,即使它曾经在,也有可能在之后被分配到hash部分,那么判断的依据是什么?到底怎样的数据,在重新分配之后会从数组部分挪到hash部分?

来看computesizes函数的实现:

static int computesizes (int nums[], int *narray) {
int i;
int twotoi; /* 2^i */
int a = 0; /* number of elements smaller than 2^i */
int na = 0; /* number of elements to go to array part */
int n = 0; /* optimal size for array part */
for (i = 0, twotoi = 1; twotoi/2 < *narray; i++, twotoi *= 2) {
if (nums[i] > 0) {
a += nums[i];
if (a > twotoi/2) { /* more than half elements present? */
n = twotoi; /* optimal size (till now) */
na = a; /* all elements smaller than n will go to array part */
}
}
if (a == *narray) break; /* all elements already counted */
}
*narray = n;
lua_assert(*narray/2 <= na && na <= *narray);
return na;
}

注意到这样的细节:这个函数在遍历nums位图数组的时候,会将当前数据数量存放在变量a中,如果a > twotoi/2,也就是当前有一半以上的空间被利用上了,那么这部分数据会继续留在数组部分,否则就会在之后挪到hash部分了.

纯粹的使用数组或者hash表性能更高

为了证实这里的判断,简单的写一段lua代码做为实验:

function print_ipairs(t)
print("in print_ipairs")
for k, v in ipairs(t) do
print(k)
end
end function print_pairs(t)
print("in print_pairs")
for k, v in pairs(t) do
print(k)
end
end a = {}
a={1,2,3,4,5,6,7,8,9,10}
print_ipairs(a)
a[2] = nil
a[3] = nil
a[4] = nil
a[6] = nil
a["k"] = "e"
print_ipairs(a)
print_pairs(a)
输出为: in print_ipairs
1
2
3
4
5
6
7
8
9
10
in print_ipairs
1
in print_pairs
1
7
8
10
k
5
9

在这里,首先对表a赋值,有1-10共十个元素,通过调用函数print_ipairs可知,这些元素都是存放在数组部分的,这是因为ipairs取的是Table的数组部分元素.

在这之后,人为的将其中2,3,4,6元素删除,造成原来数组不满一半元素被利用上的现象,然后再插入一个新key "k",以让这个Table重新分配空间.再此之后,再次调用ipairs遍历a的数组部分,可以看到只有1被打印出来了,也就是说,在重新分配空间之后,除去已经被删除的2,3,4,6之外,只有1还在数组里面,剩下的5,7,8,9,10已经不在数组部分了.紧接着调用pairs遍历这个表,可以看出这些已经不在数组部分的值又被打印出来了,并且它们的顺序已经被打乱,不再按照数字大小顺序来排列了,它们在这次重新分配中被挪动到了hash部分.

这个实验既验证了我们前面的分析,同时也告诉我们,Table的重新分配,实际上代价是很大的,因此不建议在实际程序中,一个Table即有数组部分,也有Hash部分,纯粹一些,性能上会有提升.

lua表类型的更多相关文章

  1. Lua表(table)的用法_个人总结

    Lua表(table)的用法_个人总结 1.表的创建及表的介绍 --table 是lua的一种数据结构用来帮助我们创建不同的数据类型.如:数组和字典--lua table 使用关联型数组,你可以用任意 ...

  2. mysql高并发和表类型

    高并发:http://www.cnblogs.com/wangchaozhi/p/5061378.html 表类型:http://www.xiaoxiaozi.com/2009/07/14/1171/

  3. 浅谈MySql的存储引擎(表类型)

    来源:http://www.cnblogs.com/lina1006/archive/2011/04/29/2032894.html 什么是MySql数据库 通常意义上,数据库也就是数据的集合,具体到 ...

  4. SQLite使用(一)&&选择表类型

    在SQLite中,主要有两种表类型,带rowid的表和不带rowid的表.我们利用create table 建一张表,默认都会有一个隐含名字为rowid的主键,暂且称带rowid的表为普通表.如果建表 ...

  5. SQL 用户定义表类型,在存储过程里使用表类型,表参数作参数

    .定义表类型SUTDENTTYPE,包含三个字段,分别对应学生表的NAME,SEX和PHONE.之所以如此创建,我是准备在插入新学生数据的存储过程中,以它为参数.   GO CREATE TYPE S ...

  6. Sqlserver 自定义表类型定义,使用,删除

    --创建用户自定义表类型CREATE TYPE dbo.CustomerTable AS TABLE ( Id int NOT NULL, Name char(10) NULL, PRIMARY KE ...

  7. LUA表克隆方法归纳

    lua表克隆 将lua一个表, 克隆出一份为一个独立的另外一个表. 对于一个module, 如果在require之后,获得的表对象, 不能直接修改, 例如lua缓存此表, 但是多次逻辑执行, 都使用的 ...

  8. MySQL表类型和存储引擎版本不一致解决方法

    使用的是老版本的mysql客户端Navicate 8 ,mysql 服务端用的是mysql5.6的版本,在修改版本引擎的时候出现版本不对; mysql error ‘TYPE=MyISAM’ 解决办法 ...

  9. mysql如何修改表类型(表引擎)

    参考阅读:http://www.manongjc.com/article/1205.html 最近遇到一个修改 MySQL 表类型的问题,以前在 phpmyadmin 管理 mysql 数据库时,建立 ...

随机推荐

  1. 深入理解net core中的依赖注入、Singleton、Scoped、Transient(四)

    相关文章: 深入理解net core中的依赖注入.Singleton.Scoped.Transient(一) 深入理解net core中的依赖注入.Singleton.Scoped.Transient ...

  2. 深入理解abstract class和interface

    摘自:http://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/ (如有侵权,请留言,版主将立即删除) abstract c ...

  3. python函数修饰器(decorator)

    python语言本身具有丰富的功能和表达语法,其中修饰器是一个非常有用的功能.在设计模式中,decorator能够在无需直接使用子类的方式来动态地修正一个函数,类或者类的方法的功能.当你希望在不修改函 ...

  4. 从.net角度分析 异步和多线程

    线程 进程 CPU目前都是多核心的,相当于一个大脑几块可以同时工作. 超线程CPU是指在一块CPU中,用虚拟方法将一个物理核心模拟成多个核心(如:一个单物理核心,模拟成二个核心,即所谓的二线程.)只有 ...

  5. dev richEditControl控件 设置文字 字体 大小

    Document doc = NoticeContentRichEditControl.Document; doc.BeginUpdate(); doc.Text = "需要设置格式的文字& ...

  6. scala抽象类实战

    实战一: 声明一个抽象类(SuperTeacher):(超级老师) abstract class SuperTeacher(val name : String){ var id : Int var a ...

  7. foreach 计数

    foreach(var item in list.Select((x, i) => new { x, i })){    User user = item.x;    int Idx = ite ...

  8. 将本地已有项目上传到github

    1.在github上创建一个文件 2.看本地C盘中是否有.ssh文件夹 (C:\Users\用户名\.ssh) 检测有没有.ssh文件夹:执行命令   cd ~/.ssh 如果没有的话执行git命令: ...

  9. mysql中FIND_IN_SET函数的使用

    有种需求,A和B是父子关系,B和C是父子关系,C与D亦是父子关系,以此类推,无限级 现在需要查询到某一级(包括本级)下面所有的,就需要用到FIND_IN_SET函数 select * from tab ...

  10. macaca常见错误排查

    1.Error: cannot resolve path (or pattern) 'macaca-test' 这是因为执行run命令的时候未进入用例目录 C:\Users\Tony\node_mod ...