介绍

表(Table)是Lua语言中最主要(事实上也是唯一的)和强大的数据结构。使用表,Lua语言可以以一一种简单、统一且高效的方式表示数组、集合、记录和其他很多数据结构。Lua语言也使用表来表示包( package )和其他对象。当调用函数math.sin时,我们可能认为是“调用了math库中函数sin”; 而对于Lua语言来说,其实际含义是“以字符串"sin"为键检索表math”。

Lua语言中的表本质上是一种辅助数组( associative array ),这种数组不仅可以使用数值作为索引,也可以使用字符串或其他任意类型的值作为索引( nil除外)。

Lua语言中的表要么是值要么是变量,它们都是对象(object) 。如果读者对Java或Scheme中的数组比较熟悉,那么应该很容易理解上述概念。可以认为,表是一种动态分配的对象,程序只能操作指向表的引用(或指针)。除此以外,Lua语言不会进行隐藏的拷贝( hidden copies )或创建新的表。

初始化

  1. local t = {}                                     -- 创建一个空表
  2. local t = {x = 10, y = 10}               -- 创建一个包含x,y元素的表
  3. local t = {};t.x = 10; t.y = 10        -- 创建一个包含x,y元素的表

上述2/3中创建表的方式是等价的,不过在第二种写法中,由于可以提前判断表的大小,所以运行速度更快。

删除

删除表的元素只需要把对应元素赋值为nil便可。

t.x = nil

内部结构(数组和哈希)以及运行方式

一般情况下,你不需要知道Lua实现表的细节,就可以使用它。实际上,Lua花了很多功夫来隐藏内部的实现细节。但是,实现细节揭示了表操作的性能开销情况。因此,要优化使用表的程序(这里特指Lua程序),了解一些表的实现细节是很有好处的。

Lua的表的实现使用了一些很聪明的算法。每个Lua表的内部包含两个部分:数组部分和哈希部分。

哈希部分使用哈希算法来保存和查找键。它使用被称为开放地址表的实现方式,意思是说所有的元素都保存在哈希数组中。用一个哈希函数来获取一个键对应的索引;如果存在冲突的话(意即,如果两个键产生了同一个哈希值),这些键将会被放入一个链表,其中每个元素对应一个数组项。当Lua需要向表中添加一个新的键,但哈希数组已满时,Lua将会重新哈希。重新哈希的第一步是决定新的数组部分和哈希部分的大小。因此,Lua遍历所有的元素,计数并对其进行归类,然后为数组部分选择一个大小,这个大小相当于能使数组部分超过一半的空间都被填满的2的最大的幂;然后为哈希部分选择一个大小,相当于正好能容纳哈希部分所有元素的2的最小的幂。

当Lua创建空表时,两个部分的大小都是0。因此,没有为其分配数组。让我们看一看当执行下面的代码时会发生什么:

local a = {}
for i = 1, 3 doa[i] = true
end

这段代码始于创建一个空表。在循环的第一次迭代中,赋值语句

a[1] = true

触发了一次重新哈希;Lua将数组部分的大小设为1,哈希部分依然为空;第二次迭代时

a[2] = true

触发了另一次重新哈希,将数组部分扩大为2.最终,第三次迭代又触发了一次重新哈希,将数组部分的大小扩大为4。

类似下面的代码

a = {}
a.x = 1; a.y = 2; a.z = 3

做的事情类似,只不过增加的是哈希部分的大小。

对于大的表来说,初期的几次重新哈希的开销被分摊到整个表的创建过程中,一个包含三个元素的表需要三次重新哈希,而一个有一百万个元素的表也只需要二十次。但是当创建几千个小表的时候,重新哈希带来的性能影响就会非常显著。

旧版的Lua在创建空表时会预选分配大小(4,如果我没有记错的话),以防止在初始化小表时产生的这些开销。但是这样的实现方式会浪费内存。例如,如果你要创建数百万个点(表现为包含两个元素的表),每个都使用了两倍于实际所需的内存,就会付出高昂的代价。这也是为什么Lua不再为新表预分配数组。

如果你使用C编程,可以通过Lua的API函数lua_createtable来避免重新哈希;除lua_State之外,它还接受两个参数:数组部分的初始大小和哈希部分的初始大小[1]。只要指定适当的值,就可以避免初始化时的重新哈希。需要警惕的是,Lua只会在重新哈希时收缩表的大小,因此如果在初始化时指定了过大的值,Lua可能永远不会纠正你浪费的内存空间。

当使用Lua编程时,你可能可以使用构造式来避免初始化时的重新哈希。当你写下

{true, true, true}

时,Lua知道这个表的数组部分将会有三个元素,因此会创建相应大小的数组。类似的,如果你写下

{x = 1, y = 2, z = 3}

Lua也会为哈希部分创建一个大小为4的数组。例如,执行下面的代码需要2.0秒:

for i = 1, 1000000 dolocal a = {}a[1] = 1; a[2] = 2; a[3] = 3
end

如果在创建表时给定正确的大小,执行时间可以缩减到0.7秒:

for i = 1, 1000000 dolocal a = {true, true, true}a[1] = 1; a[2] = 2; a[3] = 3
end

但是,如果你写类似于

{[1] = true, [2] = true, [3] = true}

的代码,Lua还不够聪明,无法识别表达式(在本例中是数值字面量)指定的数组索引,因此它会为哈希部分创建一个大小为4的数组,浪费内存和CPU时间。

两个部分的大小只会在Lua重新哈希时重新计算,重新哈希则只会发生在表完全填满后,Lua需要插入新的元素之时。因此,如果你遍历一个表并清除其所有项(也就是全部设为nil),表的大小不会缩小。但是此时,如果你需要插入新的元素,表的大小将会被调整。多数情况下这都不会成为问题,但是,不要指望能通过清除表项来回收内存:最好是直接把表自身清除掉。

你可能会好奇Lua为什么不会在清除表项时收缩表。首先是为了避免测试写入表中的内容。如果在赋值时检查值是否为nil,将会拖慢所有的赋值操作。第二,也是最重要的,允许在遍历表时将表项赋值为nil。例如下面的循环:

for k, v in pairs(t) doif some_property(v) thent[k] = nil – 清除元素end
end

如果Lua在每次nil赋值后重新哈希这张表,循环就会被破坏。

如果你想要清除一个表中的所有元素,只需要简单地遍历它:

for k in pairs(t) dot[k] = nil
end

一个“聪明”的替代解决方案:

while true dolocal k = next(t)if not k then break endt[k] = nil
end

但是,对于大表来说,这个循环将会非常慢。调用函数next时,如果没有给定前一个键,将会返回表的第一个元素(以某种随机的顺序)。在此例中,next将会遍历这个表,从开始寻找一个非nil元素。由于循环总是将找到的第一个元素置为nil,因此next函数将会花费越来越长的时间来寻找第一个非nil元素。这样的结果是,这个“聪明”的循环需要20秒来清除一个有100,000个元素的表,而使用pairs实现的循环则只需要0.04秒。

通过使用闭包,我们可以避免使用动态编译。下面的代码只需要十分之一的时间完成相同的工作:

function fk (k)return function () return k end
endlocal lim = 100000
local a = {}
for i = 1, lim do a[i] = fk(i) endprint(a[10]()) --> 10

取长度

常用的取长度方式为#
而#的使用又有些需要注意的地方。
首先要明确的是lua中有两部分:数组部分和hash表部分。而基本上所有操作都是先数组后hash表。

local test1 = { 1 , 2 , 3 , 4 , 5 }
print(#test1)
打印结果: 5

local test1 = { 1, 3 , 5 , 2 , 4 }
print(#test1)
打印结果: 5 (好吧。。。。当然跟上面一样,都是作为数组中的值。。。)

local test1 = {[1] = 1 , [2] = 2 , [3] = 3 , [4] = 4 ,[5] = 5}
print(#test1)
打印结果: 5 (这里table中没有数组部分了,只有hash表部分)

local test1 = {[1] = 1 , [3] = 3 , [4] = 4 , [6] = 6 ,[2] = 2}
print(#test1)
打印结果: 6

明明写的table中只有5个元素,怎么会变成6那。。。。这里的原因就要看下lua源码实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

/*

** Try to find a boundary in table `t'. A `boundary' is an integer index

** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).

*/

int luaH_getn (Table *t) {

  unsigned int j = t->sizearray;

  if (j > 0 && ttisnil(&t->array[j - 1])) {

    /* there is a boundary in the array part: (binary) search for it */

    unsigned int i = 0;

    while (j - i > 1) {

      unsigned int m = (i+j)/2;

      if (ttisnil(&t->array[m - 1])) j = m;

      else i = m;

    }

    return i;

  }

  /* else must find a boundary in hash part */

  else if (isdummy(t->node))  /* hash part is empty? */

    return j;  /* that is easy... */

  else return unbound_search(t, j);

}

还是先数组,数组没有后hash部分。再来看下关于hash表部分的取长度

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

static int unbound_search (Table *t, unsigned int j) {

  unsigned int i = j;  /* i is zero or a present index */

  j++;

  /* find `i' and `j' such that i is present and j is not */

  while (!ttisnil(luaH_getint(t, j))) {

    i = j;

    j *= 2;

    if (j > cast(unsigned int, MAX_INT)) {  /* overflow? */

      /* table was built with bad purposes: resort to linear search */

      i = 1;

      while (!ttisnil(luaH_getint(t, i))) i++;

      return i - 1;

    }

  }

  /* now do a binary search between them */

  while (j - i > 1) {

    unsigned int m = (i+j)/2;

    if (ttisnil(luaH_getint(t, m))) j = m;

    else i = m;

  }

  return i;

}

j++保证j是hash部分的第一个值,从j开始,如果j位置是有值的,那么将j扩大两倍,再检查两倍之后hash表中是否可以取到值,直到找到没有值的地方,这个值就在i 到 j这个区间中。然后再用折半查找找到 i 到 j之间找到的最后一个nil的,前面的就是它的长度了。 错略看来。luaH_getint用来取值 
const TValue *luaH_getint (Table *t, int key)而它的声明看来 ,第二个参数是key,通过key来取value, 而外面对传入的key是++的操作 可知计算长度用来寻找的这个key一定是个整形,而且还得是连续的(不一定)。(当然这个是不深究细节实现错略看下来的分析。。。。。)

遍历

ipairs遍历  从下表为1的元素开始遍历,遇到nil结束。

local t = {[5] = "105",[6] = "106",[8] = "108",}for i =1 , 4 dot[i] = i
end
for i, v in ipairs(t)  doprint(v)
end

输出:

1

2

3

4

105

106

pairs遍历,受限于表在lua语言中的底层实现机制,遍历过程中元素的出现顺序可能是随机的,相同的程序在每次运行时也可能产生不同的顺序。唯一可以确定的是,在遍历的过程中每个元素会且只会出现一次。

local t = {[105] = "105",[106] = "106",[107] = "107",[108] = "108",[109] = "109",[110] = "110",[111] = "111",[112] = "112",[113] = "113",[114] = "114",}for i =1 , 4 dot[i] = i
end
for i, v in pairs(t)  doprint(v)
end

输出:

112

113

114

3

4

2

1

105

106

107

108

109

110

111

转载自:https://blog.csdn.net/wangmanjie/article/details/52793902

https://blog.csdn.net/weixin_42973416/article/details/103294010

https://blog.csdn.net/summerhust/article/details/18599375

Lua 表(table)相关推荐

  1. Lua之table(表)

    Lua table(表) 使用表来统一表示Lua中的一切数据,是Lua区分于其他语言的一个特色.这个特色从最开始的Lua版本保持至今,很大的原因是为了在设计上保持简洁.Lua表分为数组和散列表部分,其 ...

  2. Lua 中 table(表) 的简单使用

    Lua table(表) table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数字.字典等. Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能 ...

  3. openresty开发系列15--lua基础语法4表table和运算符

    openresty开发系列15--lua基础语法4表table和运算符 lua中的表table 一)table (表) Table 类型实现了一种抽象的"关联数组".即可用作数组, ...

  4. LUA表与函数的深入理解

    LUA表与函数的深入理解 local heroInfo = {}--直接打印 table的名字,就会输出该table的内存地址 print("表地址---------------" ...

  5. LUA表 pairs, ipairs输出顺序问题

    LUA表 pairs, ipairs输出顺序问题 t = {[1] = 222,[2] = 23,[3] = 2433,[42] = 135,[5] = 1287,[7] = 7,[102] = 10 ...

  6. lua ue_slua unreal分析(二)LuaActor与lua表互访

    相关文章: 南京周润发:slua unreal分析(一)LuaActor概览​zhuanlan.zhihu.com 南京周润发:slua unreal分析( 三)slua与GC​zhuanlan.zh ...

  7. Lua 的table遍历 【转】

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://rangercyh.blog.51cto.com/1444712/1032925 ...

  8. Lua 中 table 库函数 table.concat 连接 函数

    Lua 中 table 库函数 table.concat 连接 函数 do--> table.concat 连接 函数 tab = {"a", "c", ...

  9. lua之table的使用

    本文对从数据结构,存储和使用三个角度对lua之table的使用进行小结. 数据结构: table是lua的一种数据结构,可以用来存放各种类型的元素,例如可以创建数组和字典,以及二者混合的数据结构, 例 ...

最新文章

  1. graphpad做折线图后怎么保存_农村的干豆角怎么做的?农村妹子教你两个窍门,保存2年都不会坏...
  2. 用python处理excel-使用Python操作Excel文档(一)
  3. 指针、数组、函数阶段小结
  4. 盲盒(随机概率 + 最大公约数)
  5. C#制作Windows service服务系列二:演示一个定期执行的windows服务及调试(windows service)(转载)...
  6. 各种编程语言,Linux命令行播放,Bio-Linux,Markdown编辑器等
  7. DateFormat是线程不安全
  8. HuaWei ❀ Radius协议概述
  9. 8cm等于多少像素_像素和厘米如何换算
  10. 游吟诗人之中二病犯了
  11. Flutter Tabbar 自定义选中下标 自定义Indicator
  12. ppt如何查看加载宏
  13. 2017.10.11 米加特官网重磅改版,华丽上线
  14. 信号电缆与计算机电缆区别,控制电缆与计算机电缆有什么区别
  15. C#编程-39:字符和字符串笔记
  16. 【评弹】夺印-夜访 歌词 盛小云
  17. 1946年计算机的诞生来自于,计算机诞生于哪年?
  18. flask实现文件简易服务器,可根据链接上传下载
  19. GT940MX能学C语言吗,940mx能玩csgo吗(940mx的csgo多少帧)
  20. p76 - Python 开发-内外网收集 Socket子域名DNS

热门文章

  1. android surface清空,Android:如何在surfaceDestroyed()之后重启视频预览?
  2. Flash Builder 开发视频播放器客户端
  3. Python中单引号、双引号和三引号作用及区别
  4. 查询各部门中高于部门平均工资的人员,人数及该部门的平均工资
  5. CDA数据分析师发布官方吉祥物“赵安豆”
  6. 如果提取音乐的伴奏和人声,分享两个方法给大家!
  7. 《深入理解Java虚拟机:JVM高级特性与最佳实践》书评
  8. **微信难逃7年之痒,子弹短信来势汹汹!**
  9. 如何在C语言中输入带分数,带分数
  10. Java FFmpeg的音视频处理