Lua 5.3 table索引异常行为求解:#table结果为何不符合预期?
#table运算符的行为解析 核心规则:#计算的是「序列长度」
Lua的#运算符并非返回table的总元素数,它的作用是获取序列(sequence)的长度。序列的定义是:table中从索引1开始,连续整数索引对应的元素都不为nil,直到第一个nil出现的位置。但如果table存在不连续的整数索引(即存在间隙),此时#的结果是不确定的——它会返回一个「边界值」n,满足t[n] ~= nil且t[n+1] == nil,若存在多个这样的n,Lua可能返回其中任意一个。
你的测试代码分析
第一组带table.insert的测试
重点看folder2的情况:
folder2 = {1, 2, 3} table.insert(folder2, "test") --4 folder2[6] = "test2" print("#folder2: " .. #folder2) --输出6
调用table.insert(folder2, "test")时,Lua会把table的数组部分扩展到索引4,此时数组部分的大小为4。之后设置folder2[6] = "test2",由于索引6和当前数组末尾4的差值较小,Lua会选择扩展数组部分到6(内部逻辑会尽量将连续整数索引放入数组部分,减少哈希部分开销)。此时数组部分索引1-4有值、索引5为nil、索引6有值,存在两个合法边界值:4(folder2[4]~=nil且folder2[5]==nil)和6(folder2[6]~=nil且folder2[7]==nil),Lua返回6符合规则——这种间隙场景下结果本身就是不确定的。
而folder3中设置folder3[7] = "test2",索引7和数组末尾4差值较大,Lua不会扩展数组部分,而是把索引7存入哈希部分,此时数组部分的边界是4,所以#返回4。
folder1的索引1-5完全连续,属于标准序列,#返回5符合预期。
第二组无table.insert的测试
folder2 = {1, 2, 3} folder2[5] = "test2" print("#folder2: " .. #folder2) --输出3
直接设置索引5时,Lua不会自动扩展数组部分,而是把索引5存入哈希部分。此时序列的边界是3(folder2[3]~=nil且folder2[4]==nil),所以#返回3,符合预期。
为什么rawlen也有同样的行为
rawlen和#的逻辑完全一致,它仅返回table数组部分的长度,不会统计哈希部分的元素,因此也无法获取table的总元素数。如果需要统计所有元素的数量,需手动遍历计数:
function table_size(t) local count = 0 for _ in pairs(t) do count = count + 1 end return count end
总结
#和rawlen只关注table的连续整数索引序列部分,不统计哈希部分的元素;- 当table存在整数索引间隙时,
#的结果是不确定的,可能返回多个合法边界值之一; table.insert会调整数组部分的大小,后续设置接近数组末尾的整数索引时,Lua可能扩展数组部分,导致#的结果变化。
内容的提问来源于stack exchange,提问作者Culain

