介绍
表(Table)是Lua语言中最主要(事实上也是唯一的)和强大的数据结构。使用表,Lua语言可以以一一种简单、统一且高效的方式表示数组、集合、记录和其他很多数据结构。Lua语言也使用表来表示包( package )和其他对象。当调用函数math.sin时,我们可能认为是“调用了math库中函数sin”; 而对于Lua语言来说,其实际含义是“以字符串"sin"为键检索表math”。
Lua语言中的表本质上是一种辅助数组( associative array ),这种数组不仅可以使用数值作为索引,也可以使用字符串或其他任意类型的值作为索引( nil除外)。
Lua语言中的表要么是值要么是变量,它们都是对象(object) 。如果读者对Java或Scheme中的数组比较熟悉,那么应该很容易理解上述概念。可以认为,表是一种动态分配的对象,程序只能操作指向表的引用(或指针)。除此以外,Lua语言不会进行隐藏的拷贝( hidden copies )或创建新的表。
初始化
- local t = {} -- 创建一个空表
- local t = {x = 10, y = 10} -- 创建一个包含x,y元素的表
- 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 do a[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 do local a = {} a[1] = 1; a[2] = 2; a[3] = 3 end
如果在创建表时给定正确的大小,执行时间可以缩减到0.7秒:
for i = 1, 1000000 do local 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) do if some_property(v) then t[k] = nil – 清除元素 end end
如果Lua在每次nil赋值后重新哈希这张表,循环就会被破坏。
如果你想要清除一个表中的所有元素,只需要简单地遍历它:
for k in pairs(t) do t[k] = nil end
一个“聪明”的替代解决方案:
while true do local k = next(t) if not k then break end t[k] = nil end
但是,对于大表来说,这个循环将会非常慢。调用函数next时,如果没有给定前一个键,将会返回表的第一个元素(以某种随机的顺序)。在此例中,next将会遍历这个表,从开始寻找一个非nil元素。由于循环总是将找到的第一个元素置为nil,因此next函数将会花费越来越长的时间来寻找第一个非nil元素。这样的结果是,这个“聪明”的循环需要20秒来清除一个有100,000个元素的表,而使用pairs实现的循环则只需要0.04秒。
通过使用闭包,我们可以避免使用动态编译。下面的代码只需要十分之一的时间完成相同的工作:
function fk (k) return function () return k end end local lim = 100000 local a = {} for i = 1, lim do a[i] = fk(i) end print(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 |
|
还是先数组,数组没有后hash部分。再来看下关于hash表部分的取长度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
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 do
t[i] = i
end
for i, v in ipairs(t) do
print(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 do
t[i] = i
end
for i, v in pairs(t) do
print(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