首页 > 代码库 > Lua 表取长度

Lua 表取长度

稀疏数组的长度返回的不对,不知道算不算是个 bug?(在 Lua5.2.2 中测试的)
如果一个表容量一半的元素,用 ‘#‘ 取长度取的不对。但是对于小于容量一半的情况,表现的是对的。
根据 Lua 的代码实现,‘#’号取表长度时取得是表中第一个空元素前的那个位置。
原文是(luaH_getn 注释) :
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).

看下例子:

a = {}
for i = 1, 2 do
    a[i] = i*3
end
a[4] = 811

用 #a 取得的结果是 4,而根据上面的描述应该取回 2。

再如:

for i = 1, 4 do
    a[i] = i*3
end
a[8] = 811

用 #a 取得的结果是 8,而根据上面的描述应该取回 4。
在老一点的版本里可以用 table.maxn 取最大下标,新版本里这个接口被废除了。
简单的分析下原因,当表中的元素很少而下标的跨度很大时,也就是对于很稀疏的数组,存储的时候存在表的哈希部分。但当数组不是那么稀疏的时候,就存在了数组部分,就是按下标从 1 递增而来。这也是对空间使用的一种优化。
看下取长度的实现(只截取对分析有用的部分)

unsigned int j = t->sizearray;
if (j > 0 && ttisnil(&t->array[j - 1])) {
  // some codes
}
/* else must find a boundary in hash part */
else if (isdummy(t->node)) /* hash part is empty? */
  return j; /* that is easy... */
//some other codes

注意 if 里后面的那个表最后的一个元素判空操作,此时对于上面的两段代码 a[4] 或 a[8],刚好它们位于数组的最后一个元素,所以不是空,if 里面不执行。由于这里的表没有哈希部分,所以,到 else if 里直接返回表长,也就是上面看到的 4 或 8。

不过,实际中,对于稀疏数组不要用 ‘#‘ 取长度,因为这个操作没有什么意义。