Lua—String拼接几种方式及相应性能权衡

14,584 阅读5分钟

1.可以使用连接操作符..来进行字符串连接。

  • 如果操作数中存在数值,那么lua语言会先把数值转换成字符串 。

"Hello" .. "World"   -->  Hello World

"result is" .. 3          -->  result is 3

  • 在某些语言中,字符串连接使用的是加号,但实际上lua中,3+5和3..5是不一样的。

print(3 + 5)   -->  8

print(3 .. 5)   -->   35

  • 使用限制:

  • 连接的元素只能是数字或字符串

  • 在连接数字时,与数字中间一定要有空格,不然会报错。在书写时,最好保证无论连接什么类型,..左右都有空格。

print(3..5)    -->  script.lua:2: malformed number near '3..'

print(3 .. {})  -->    script.lua:2: attempt to concatenate a table value
stack traceback:
	script.lua:2: in main chunk
	[C]: in ?

Exited with error status 1
  • 性能分析:

使用运算符..,每次拼接都需要申请新的空间,旧的 result 对应的空间会在某时刻被Lua的垃圾回收期GC,且随着result不断增长,越往后会开辟更多新的空间,并进行拷贝操作,产生更多需要被GC的空间,所以性能降低。 

2.使用 table.concat () 函数来进行字符串连接。

  •  table.concat(table, sep, start, end)函数列出table数组部分从start位置到end位置的所有元素,元素之间以分隔符sep隔开。 

    fruits = {“banana”, “orange”, “apple”}
    print(table.concat(fruits)) --> bananaorangerapple
    print(table.concat(fruits,”,”)) --> banana,orange,apple print(table.concat(fruits, ”,”, 2, 3)) --> orange, apple

  • 使用限制:

  • 连接元素必须是字符串或数字。

  • 必须先把所有待拼接元素放入一个table里面,使用起来可能不太方便

  • 源码:

  • concat内部实现函数是tocancat,如下:

    static const lua_Reg tab_funcs[] = { {"concat", tconcat}, ... }

  • 下面简单解释一下toconcat函数

    static int tconcat (lua_State *L) { // table.concat的参数都存放在栈上,可以简单理解为: // 栈1号位存的是table // 栈2号位存的是sep // 栈3、4号位存的是起始(start)和结束(end)的位置

    // 先从2号位置取出sep,如果2号位置是nil,即没有指定sep,则sep=""
    const char *sep = luaL_optlstring(L, 2, "", &lsep);
    
        // 检查栈1号位置存的数据类型是否是一个table
    luaL_checktype(L, 1, LUA_TTABLE);
    
    // i table起始索引,如果起始索引为nil,则默认从1开始
    i = luaL_optint(L, 3, 1);
    
        // last table结束索引,如果结束索引为nil,则默认为table数组部分大小。
    last = luaL_opt(L, luaL_checkint, 4, luaL_getn(L, 1));
    
        // 申请一块buff,存合并后的数据,初始buff大小为8192
        // 当buff大小用完后就直接用luaV_concat在栈上做字符串连接
    luaL_buffinit(L, &b);
    
        // 把table里面索引从i到last的值取出来,并放到buff里面,buff大小为BUFSIZ(8192)
        // 每当写满一个buff,就把buff生成一个TString,并放到栈上,并把buff清空重新写
    for (; i < last; i++) {
    	addfield(L, &b, i);
    	luaL_addlstring(&b, sep, lsep);
    }
    
    // 把最后一个元素放入buff
    // 把buff生成一个TString,并放到栈上
    if (i == last) 
    	addfield(L, &b, i);
    
    // 把栈上之前所有生成的TString通过luaV_concat(就是 .. 语法糖的合并函数)合并成一个TString,放到栈上,相当于返回值,供上层函数取用
    luaL_pushresult(&b);
    return 1;
    

    }

  • 性能分析:

    从源码上看,table.concat没有频繁申请内存,只有当写满一个8192的BUFF时,才会生成一个TString,最后生成多个TString时,会有一次内存申请并合并。在大规模字符串合并时,应尽量选择这种方式。

    table.concat 底层拼接字符串的方式也是使用运算符.. ,但是其使用算法减少了使用运算符..的次数,减少了GC,从而提高效率。

3.使用格式化string.format(“%s%s%s”,str1,str2,str3)进行字符串拼接

Lua string模块内置的format函数,和c语言的printf类似,可以将不同类型的数据格式化成字符串。

  • 格式:string.format(fmt, [...])

  • 格式化字符串可能包含一下转义码:

    %c - 接受一个数字, 并将其转化为ASCII码表中对应的字符 %d, %i - 接受一个数字并将其转化为有符号的整数格式 %o - 接受一个数字并将其转化为八进制数格式 %u - 接受一个数字并将其转化为无符号整数格式 %x - 接受一个数字并将其转化为十六进制数格式, 使用小写字母 %X - 接受一个数字并将其转化为十六进制数格式, 使用大写字母 %e - 接受一个数字并将其转化为科学记数法格式, 使用小写字母e %E - 接受一个数字并将其转化为科学记数法格式, 使用大写字母E %f - 接受一个数字并将其转化为浮点数格式 %g(%G) - 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式 %q - 接受一个字符串并将其转化为可安全被Lua编译器读入的格式 %s - 接受一个字符串并按照给定的参数格式化该字符串

  • 为进一步细化格式,可以在%号后面添加参数,参数将以如下的顺序读入:

  • 符号:一个+号表示其后的数字转义符将让正数显示正号,默认情况下只有负数显示符号。

  • 占位符:一个0,在后面指定了字符串宽度时占位用,不填0时的默认占位符是空格。

  • 对齐标识:在指定了字符串宽度时,默认为右对齐,增加-号可变为左对齐。

  • 宽度数值

  • 小数位数/字串裁切:在宽度数值后增加的小数部分n, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留n位, 若后接s(字符串转义符, 如%5.3s)则设定该字符串只显示前n位。

  • 实例:

    string1 = "mynameisbb"; number1 = 123456 number2 = 123.456 print(string.format("%.4s,%+08d,%.2f",string1,number1,number2)) --> myna,+0123456,123.46

  • 性能分析:

  • 字符串的连接指令比格式化要安全一些,因为格式化函数字符串的长度限制在512。而连接字符串没有这个限制。

  • 格式化字符串的操作比较复杂,消耗会多一些。

4.string.rep

lua string内置模块中另一个可以做字符串连接是rep,不过使用局限性大,只能重复的对某一个字符串做N次拼接。

示例:

string.rep("abc",3)   -->  abcabcabc

性能总结

  • table.concat()优于字符串连接符.. 

  • 字符串连接符优于格式化字符串 

其中部分内容参考学习blog.csdn.net/fengshenyun…