字符串连接符 “..”

当需要把多个小字符串拼接成一个大串的时候,例如,从一个sql结果集中取某列元素并将该元素组成以‘/’分割的字符串。

如果sql结果集是上万行,那么就会发现效率越来越低。这性能越来越低的原因是什么呢?这就要去追溯lua的垃圾收集算法,当Lua虚拟机发现程序使用太多的内存,它就会遍历它所有的数据结构,并进行释放它认为是垃圾的数据。一般情况下,这个算法有很好的性能,但下面的那段代码loop使得算法的效率极其低下。假设在loop中间,buff的大小已经是一个10kb的字符串, 一个sql结果中的所需要串连的成员的大小为10bytes, 那么buff .. row_tab.name .. “/” 新建的buff大小为10k+11bytes, 并且从buff中将10KB的字符串copy到新串中,也就是说每一行,都要移动10KB的内存,串连100行的时候(差不多1KB),Lua已经移动了1MB的内存。

local buff = ''
for _, item in pairs(sql_tab) do
    if buff ~= '' then
        buff = buff .. "/" .. item 
    else
        buff = item
    end 
end

也就是赋值语句中老的字符串变成了垃圾数据,两轮循环之后,将有两个老串包含超过20KB的垃圾数据,这个时候lua虚拟机会进行垃圾收集并释放这20KB的内存,问题在于,每两次循环就要进行一次垃圾收集,读取完一个sql结果集,所需的内存是原本内容的三倍。

而要获取最终的字符串,同时避免过多的垃圾回收,我们可以使用table.concat函数将一个列表的所有元素合并。

local buff = ''
local tmp_tab = {}
for _, item in pairs(sql_tab) do
    table.insert(tmp_tab, item)
end
buff = table.concat(tmp_tab, "/")


获取table的元素个数

当一个table为数组的时候,我们可能会使用以下两种用法中的一种,来获取该table的元素个数

我们修改table:

tab = {1, 4, 5, 8}
print(#tab)                    --=====> 4
print(table.getn(tab))         --=====> 4

tab[9] = 10
print(#tab)                    --=====> 4
print(table.getn(tab))         --=====> 4
print(tab[9])                  --=====> 10

 

使用上述的两种方法,发现返回的table元素依旧是4。为什么呢,我们再把上面的代码修改为:

tab = {1, 4, 5, 8}
print(#tab)                    --=====> 4
print(table.getn(tab))         --=====> 4

tab[5] = 10
print(#tab)                    --=====> 5
print(table.getn(tab))         --=====> 5
print(tab[5])                  --=====> 10

我们可以看到,这段代码可以得到我们想要的结果。把table当数组使用的时候,该数组的大小不固定,可动态增长,在Lua中可以通过整数下标访问数组中的元素。当跳过数组下标时,像前一段代码一样,tab[5]、tab[6]、tab[7]、tab[8]的值均为nil,而table.getn和#tab两个函数一遇到为nil的时候就会返回了,就出现之前的结果(同样适用于table当集合使用,其结果返回为0)

函数重名:

在C语言或其他静态语言中,会对函数名进行检查,不允许重名的函数出现,但在lua,重名的函数是允许出现的,这就给我们的编码埋下一些隐患。当项目达到一定规模的时候,就很难保证不出现重名函数。那重名的函数会导致什么问题呢?我们看下面的代码:

function fun_a()
    print('a')
end

fun_a()                 --==========> a

function fun_a()
    print('b')
end

fun_a()                 --==========> b

原来lua虚拟机会把lua中的函数名都作为局部变量,存在局部变量表里,并在栈上开辟一个寄存器空间,在运行期,将新建一个closure,并存在已保留的寄存器里面。当有一个新定义的函数加入时,会新建一个closure,lua会把它压入栈,在调用该函数的时候,就会从栈顶开始找,找到匹配的函数名则返回。

 

堆栈溢出:

我们在运行lua的时候,有可能遇到这样一种报错 “stack overflow”,先看看下面一段代码

function func_r(a)
    a = a + 1
    if a > 100000 then
        print(a)
    else
        func_r(a)
    end
    return a
end

x = 1
func_r(x)

语法上的确没有任何问题,但在执行的时候就会出现 stack overflow 的报错。是什么原因导致堆栈溢出呢?这个就要追究到Lua源码:Lua虚拟机会对堆栈进行一系列的检查(函数:luaL_checkstack),错误类型就有:

“too many arguments”,

“assume array is smaller than 2^40 “,

“string slice too long”,

“too many captures”,

“too many arguments to script”,

“too many nested functions”

例如,上面的代码就属于递归嵌套次数太多,默认限制20000。