lua的table表处理 及注意事项

lua,一款很轻量级很nice很强大的脚本语言,做为lua中使用最为频繁的table表,在使用之时还是有颇多的好处与坑的;

下面是大牛 云风的一片关于lua table的blog,可使得对lua table内在机制 窥测一二;

lua 的整体效率是很高的,其中,它的 table 实现的很巧妙为这个效率贡献很大。

lua 的 table 充当了数组和映射表的双重功能,所以在实现时就考虑了这些,让 table 在做数组使用时尽量少效率惩罚。

lua 是这样做的。它把一个 table 分成数组段和 hash 段两个部分。数字 key 一般放在数组段中,没有初始化过的 key 值全部设置为 nil 。当数字 key 过于离散的时候,部分较大的数字 key 会被移到 hash段中去。这个分割线是以数组段的利用率不低于 50% 为准。 0 和 负数做 key 时是肯定放在 hash 段中的。

string 和 number 都放在一起做 hash ,分别有各自的算法,但是 hash 的结果都在一个数值段中。hash 段采用闭散列方法,即,所有的值都存在于表中。如果hash 发生碰撞,额外的数据记在空闲槽位里,而不额外分配空间存放。当整个个表放满后,hash 段会扩大,所有段内的数据将被重新 hash ,重新 hash 后,冲突将大大减少。

这种 table 的实现策略,首先保证的是查找效率。对于把 table 当数组使用时将和 C 数组一样高效。对于 hash 段的值,查找几乎就是计算 hash 值的过程(其中string 的 hash 值是事先计算好保存的),只有在碰撞的时候才会有少许的额外查找时间,而空间也不至于过于浪费。在 hash 表比较满时,插入较容易发生碰撞,这个时候,则需要在表中找到空的插槽。lua 在table 的结构中记录了一个指针顺次从一头向另一头循序插入来解决空槽的检索。每个槽点在记录 next 指针保存被碰撞的 key 的关联性。

整个来说,这种解决方法是非常不错的。

关于映射表的实现,我前段时间也做过一个别的研究。贴在留言本上:

<a href="http://www.codingnow.com/2004/board/view.php?paster=777&reply=0">树表结合的一种映射表实现</a>

<a href="http://www.codingnow.com/2004/board/view.php?paster=776&reply=0">在 vector , map , list 间取得平衡</a>

原文链接: http://blog.codingnow.com/2005/10/lua_table.html

即便作为lua 开发蛮久的coder来讲,很多东西不亲自去考究一下,还不是很清晰的,不如lua table的长度问题就是一个 很奇葩的例子;下面的这些也许你就不是很清楚了;

想要取得lua table长度,有这么几种方法,table.getn(table_name), #table_name, table.maxn(table_name), 再加上 ipairs(table_name) 和pairs(table_name)遍历;

根据云风所写的文章可以得知,lua的两种不同的存储方式,自然的,上述的几种 取得lua table 长度的几种方式也 存在区别;

table.maxn(table)

table.maxn()函数返回指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0. 此函数不限于table的数组部分.

table.getn(table)

返回table中元素的个数

#(table)

返回的是lua table 中key为连续整型数字(抑或是 默认整型)的长度数;

pairs()函数基本和ipairs()函数用法相同, 区别在于pairs()可以遍历整个table, 即包括数组及非数组部分.

关于对lua table长度的问题,http://blog.csdn.net/dssdss123/article/details/12676329 对于一些奇葩问题的讲述还是有些深入的;亲测【lua version=5.1.4】,的确如此;

但是还是有些东西需要补充的,对于 #用法,其表现和table.getn()在很多极端的情况下都是类似的;table.maxn(),因为获取的是table中所有正数key值中最大的key值.可以不连续;

local tblTest =
{
    "this 1",
    "this 2",
    [3] = 2,
    [4] = 5,
    [5] = 7,
    "this 3",
    [10] = 10,
}
print(table.getn(tblTest))
print(#(tblTest))
print(table.maxn(tblTest))

--===========================
>lua -e "io.stdout:setvbuf 'no'" "filedeal.lua" 
5
5
10
--===========================

local tblTest =
{
    "this 1",
    [3] = 2,
    [4] = 5,
    [5] = 7,
    "this 3",
    --"adsfasd",
    [10] = 10,
}
print(table.getn(tblTest))
print(#(tblTest))
print(table.maxn(tblTest))
--===========================
>lua -e "io.stdout:setvbuf 'no'" "filedeal.lua" 
5
5
10
--===========================


local tblTest =
{
    "this 1",
    [3] = 2,
    [4] = 5,
    [5] = 7,
    --"adsfasd",
    [10] = 10,
}
print(table.getn(tblTest))
print(#(tblTest))
print(table.maxn(tblTest))
--===========================
>lua -e "io.stdout:setvbuf 'no'" "filedeal.lua" 
1
1
10
--===========================
    

上述三个tabTest 的不同在于 这都是lua默认的下表是从1开始,有两个的默认的,使得 [3]=2,这项元素足以将连续性接上,当接不上的时候,因为不连续行,自然打印值有所不同了;

倘若在后面或者该表 其中再坠入一个nil,所输出来的内容又是不一样的:所以~不要在lua的table中使用nil值,如果一个元素要删除,直接remove,不要用nil去代替。

倘若再将元素后面加一项 下表默认的元素,其结果会怎样呢? 例 如下代码:

local tblTest =
{
    "this 1",
    [3] = 2,
    [4] = 5,
    [5] = 7,
    "this 3",
    --"adsfasd",
    [10] = 10,
}
for k , v in ipairs(tblTest) do
    print(k,v)
end
--=============================
1    this 1
2    this 3
3    2
4    5
5    7
--=============================

local tblTest =
{
    "this 1",
    [3] = 2,
    [4] = 5,
    [5] = 7,
    "this 3",
    "this 4",
    --"adsfasd",
    [10] = 10,
}
for k , v in ipairs(tblTest) do
    print(k,v)
end

--=============================
1    this 1
2    this 3
3    this 4
4    5
5    7
--=============================

由此可见,lua 默认的下表值会将显示的覆盖,即便是 再调整下顺序 也是如是,至于为何如此,不理解,有待参悟【欢求 大神指正】;

这样的意外在lua中 挺多的,只要明白了 基本的原理,倒也不足为奇了;

至于lua table的遍历 可参见 http://rangercyh.blog.51cto.com/1444712/1032925 这这篇文章;讲的挺详细的;

另外: 关于lua table的其他小问题:

1, 配置lua table 元素之间,以”,“ 或者”;“完全是一样的【可参见lua手册】,看你的爱好了,推荐的是:用分好可以作为 元素类型的不同而分割开显示下;

2,不要在lua的table中使用nil值,如果一个元素要删除,直接remove,不要用nil去代替。

3,判断lua table 是否为nil 不能用 if a == {} then 【错误的】(这样的结果就是a == {}永远返回false,是一个逻辑错误。因为这里比较的是table a和一个匿名table的内存地址。);

  if table.maxn(a) == 0 then 【错误的】这样做不保险啊,除非table的key都是数字,而没有hash部分。

  if #(a) == 0 then 也是不靠谱的,除非你能保证没人这样写这个table like this:tab = {nil,1,} 用#tab print出来 的确是0, 能说此tab是nil的?

  可以使用lua内置的next来判断; if next(a) == 0 then ;

4,应该尽量使用 local 变量而非 global 变量。这是 Lua 初学者最容易犯的错误。 global 变量实际上是放在一张全局的 table 里的。 global 变量实际上是利用一个 string (变量名作 key) 去访问这个 table 。 虽然[InterWiki]Lua5 的 table 效率很高 ,但是相对于 local 变量,依然有很大的效率损失。 local 变量是直接通过 Lua 的堆栈访问的。有些 global 变量的访问是不经意的,比如双重for中使用的pairs或者ipirs(全局函数),如果在使用循环外层 local pairs=pairs会对性能有些不同层次的提升;

5, 警惕临时变量 字符串的连接操作,会产生新的对象。这是由 lua 本身的 string 管理机制导致的。lua 在 VM 内对相同的 string 永远只保留一份唯一 copy ,这样,所有字符串比较就可以简化为地址比较。这也是 lua 的 table 工作很快的原因之一。这种 string 管理的策略,跟 java 等一样,所以跟 java 一样,应该尽量避免在循环内不断的连接字符串,比如 a = a..x 这样。每次运行,都很可能会生成一份新的 copy 。

6, 同样,记住,每次构造一份 table 都会多一份 table 的 copy 。比如在 lua 里,把平面坐标封装成 { x, y } 用于参数传递,就需要考虑这个问题。每次你想构造一个坐标对象传递给一个函数,{ 10,20 } 这样明确的写出,都会构造一个新的 table 出来。要么,我们想办法考虑 table 的重用;要么,干脆用 x,y 两个参数传递坐标。 同样需要注意的是以 function foo (...) 这种方式定义函数, ... 这种不定参数,每次调用的时候都会被定义出一个 table 存放不定数量的参数。 这些临时构造的对象往往要到 gc 的时候才被回收,过于频繁的 gc 有时候正是效率瓶颈。

7,【未完待续...】