Lua字符串拼接

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua字符串拼接相关的知识,希望对你有一定的参考价值。

参考技术A 之前研究lua中字符串拼接,看了一些文章都说 "table.concat" 高于 ".."。最近项目做优化,发现项目中使用table.concat的效率并不比..高,所以实际测试了一下。

1、一些文章说的"table.concat" 高于 "..",是在特定环境中才有效的,看一下他们使用的测试用例:

local str = "a"

local count = 100000

local start_time = os.clock()

local result = ""

for i=1,count do

    result = result .. str

end

print("operatorConcatTime:" .. os.clock() - start_time)

local tbl =

for i=1,count do

    table.insert( tbl,str)

end

table.concat(tbl)

print("tableConcatTime:" .. os.clock() - start_time)

运行后测试结果如上图,运行效率明显table.concat远高于..。在上述测试用例中,“..” 每次只拼接一个字符串,一共拼了10000次。“..”每次拼接都会产生一个新的字符串,而在lua中每产生一个新的字符串都需要将该字符串存放在全局状态表(global_State)的 strt 域中,随着拼接次数增大,就会需要更大的空间存储新的字符串,当达到一定大小时,旧的字符串就需要GC,伴随着不断的开辟新空间和GC,就导致性能降低。 而table.concat 底层拼接字符串的方式也是使用运算符.. ,但是其使用算法减少了使用运算符..的次数,减少了GC,从而提高效率。主要思路:采用二分思想,用栈存储字符串,新入栈的字符串与下方的字符串比较长度,大于则使用运算符..拼接成新字符串,并移除栈顶的字符串,不断向下直至遇到长度更大的字符串或者栈底,这样保持最大的字符串位于栈底,栈呈现金字塔的形状,最终在使用运算符..将栈中的字符串拼接成最终的字符串。引用( Lua大量字符串拼接方式效率对比及原因分析_AaronChan的博客-CSDN博客_lua 字符串拼接 )。

2、而在实际项目中一般都是几个字符串的拼接,拼接频次高,拼接个数少。针对项目中实际情况,写了如下测试用例测试:

(1)测试用例1:

function global_SpliceString (...)

     local t = ...

    return table.concat (t)

end

local count = 10 000

local sM1 = 0

local sM2 = 0

local start_time

collectgarbage("collect")

sM1 =collectgarbage("count")

print("sM1:",sM1)

local result = ""

start_time = os.clock()

for i = 1, count do            

     result =global_SpliceString("SELECT * FROM ", "QuestPlot", "WHERE sn='", i, "'")

end

print("CostTime:",(os.clock() - start_time))

sM2 =collectgarbage("count")

print("sM2:",sM2)

print("Genery Mem ory:" , (sM2 - sM1))

(2)测试用例2:

local  count = 10 000

local  sM1 =  0

local  sM2 =  0

local start_time

collectgarbage("collect")

sM1 = collectgarbage("count")

print("sM1:",sM1)

local result = ""

start_time = os.clock()

for i = 1, count do

     result = "SELECT * FROM" .. "QuestPlot" .. " WHERE sn='" .. i .."'"

end

print("CostTime:",(os.clock() - start_time))

sM2 =collectgarbage("count")

print("sM2:",sM2)

print("Genery Memory:",(sM2 - sM1))

(3)测试用例3:

local tb = [1]= "SELECT * FROM ", [2] = "QuestPlot", [3] = " WHEREsn='", [4] = 100101, [5] = "'"

collectgarbage("collect")

sM1 =collectgarbage("count")

print("sM1:",sM1)

local result = ""

start_time = os.clock()

for i = 1, count do

     tb[4] = i

     result = table.concat( tb )

end

print("CostTime:",(os.clock() - start_time))

sM2 =collectgarbage("count")

print("sM2:",sM2)

print("Genery Memory:",(sM2 - sM1))

测试结果如下所示:

(1)global_SpliceString:

(2)..

(3)table.concat:

为了模仿更真实的使用环境,以上三个测试用例中要拼接的字符串都有一个动态变化的字符串。通过结果比对,测试用例2和用例3的耗时和内存相差无几,而测试用例1的耗时和内存明显高出很多。通过查看lua源码知道,在一个语句中用“..”连续拼接几个字符串并不会产生中间字符串,而是会把所有需要连接的字符串都收集起来一起连接。所以table.concat和“..”的方式相差无几。再看测试用例1,同样使用table.concat,为什么用例1的耗时和内存明显增多。 是因为在 gobal_SpliceString() 方法中的 local  t = ... , 每次调用这个方法都需要先构造一个table,然后才能使用table.concat。而table在lua中也属于GC对象,table的创建需要消耗更多时间和内存。同时调用global_SpliceString也会有函数调用消耗,所以用例1的性能消耗是由构造table导致的。

3、总结:

通过以上分析,我们不能简单的说table.concat和“..”谁的性能更好,还要根据具体使用场景,具体问题具体分析。一般如果一次拼接大量字符串并且要拼接的字符串基本都是固定的,可以使用table缓存起来,使用table.concat拼接;如果是动态的且比较少的字符串拼接,可以直接在一个语句中使用“..”连续拼接。

Lua中的字符串拼接浅析

    在使用Lua时,字符串的拼接是最最常见的操作之一,而在Lua中完成字符串拼接的方式大概有以下四种:

1、运算符..

2、table.concat()

3、string.format()

4、string.rep()

    那么在实现字符串拼接时选用什么方式能够提升效率呢?

    首先大家要明白在Lua中会维护一份所有字符串的拷贝,并且只保存字符串的引用,在进行字符串操作时实际上并没有影响到字符串的本身,而是改变了对字符串的引用,并且当字符串没有受到引用时将会在GC阶段回收掉。

所以为了提高字符串拼接的效率,应当尽量减少产生中间字符串的次数

一、运算符..

在使用..时需要注意以下限制:

(1)当第一个拼接的元素是数字时必须在数字后加上空格防止错误解释。

(2)拼接的元素只能是数字或者字符串

print("hello".."world")print(1 .."qwer")

在第一个字符串拼接的过程中Lua会先创建出两个字符串分别为 "hello" 和"world",然后通过CONCAT函数来完成字符串的拼接。

void luaV_concat (lua_State *L, int total, int last) { do {      // 计算需要连接的字符串的长度 size_t tl = tsvalue(top-1)->len; for (n = 1; n < total && tostring(L, top-n-1); n++) { size_t l = tsvalue(top-n-1)->len; if (l >= MAX_SIZET - tl) luaG_runerror(L, "string length overflow"); tl += l;      } // 申请对应长度的内存    buffer = luaZ_openspace(L, &G(L)->buff, tl); // 通过memcpy拼接字符串 for (i=n; i>0; i--) { /* concat all strings */ size_t l = tsvalue(top-i)->len; memcpy(buffer+tl, svalue(top-i), l); tl += l; }}

    需要注意的是:不是每个拼接符的使用都会产生出中间字符串,在同一语句中使用多个..来连续拼接时会将所有需要拼接的字符串全部收集起来一起拼接,所以在较少字符串需要拼接时可以直接通过运算符..来连续拼接。

二、table.concat(table,step,start,end):实际上是将多个字符串用table缓存起来完成拼接,将table的数组部分从start到end的元素以指定的step拼接起来。其中step是可指定的拼接字符。

local result = {}for i=0,10 do result[i] = "q"endlocal string_result=table.concat( result,"wer", 1, 10 )print(string_result)

输出结果如下:

concat的实现是通过tconcat来完成的,其源码如下:

static int tconcat (lua_State *L) {  // 取出输入的step参数,若step为nil,则sep=""  const char *sep = luaL_optlstring(L, 2"", &lsep); // 检查输入的table参数类型是否为table  luaL_checktype(L, 1, LUA_TTABLE); // i table起始索引,如果起始索引为nil,则默认从1开始  i = luaL_optint(L, 31); // 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将会大大减少了申请新内存的次数,只有需要拼接的字符串写满了一个buff大小或者输入完成时才会生成一个Tstring,所以一定程度上减少了对运算符..的使用,从而提高了拼接的效率。

但是使用table.concat需要先构造一个table,而table同样属于GC的对象,并且相对于字符串而言table的创建需要消耗更多的时间和内存,所以只有在需要拼接大量字符串时再采用这种方式。

三、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, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格.对齐标识: 在指定了字串宽度时, 默认为右对齐, 增加-号可以改为左对齐.宽度数值小数位数/字串裁切: 在宽度数值后增加的小数部分n, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留n位, 若后接s(字符串转义符, 如%5.3s)则设定该字符串只显示前n位.

在这里的使用是与C的sprintf类似的,其源码的实现是str_format函数

static int str_format (lua_State *L) {  int arg = 1; // 从栈上获取第一个参数:fmt,两个指针分别指向该字符串的起止位置 const char *strfrmt = luaL_checklstring(L, arg, &sfl);  const char *strfrmt_end = strfrmt+sfl; // 初始化一个8192大小的BUFF,格式化的字符串将会存入其中。 luaL_Buffer b; luaL_buffinit(L, &b);  while (strfrmt < strfrmt_end) { // 非%...的直接写入BUFF if (*strfrmt != L_ESC) luaL_addchar(&b, *strfrmt++);  // %% 直接写入% else if (*++strfrmt == L_ESC) luaL_addchar(&b, *strfrmt++); /* %% */ else { /* format item */ // 每找到一个%... 就写入form // 把找到的%...,格式化后的结果写入buff char form[MAX_FORMAT]; /* to store the format (`%...') */ char buff[MAX_ITEM]; /* to store the formatted item */  strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { // 对%c做具体格式化 case 'c': { sprintf(buff, form, (int)luaL_checknumber(L, arg)); break; } ...      } // 将格式化后结果写入BUFF // 如果BUFF大小不够,则先将BUFF生成一个TString,并压入栈上,清空BUFF重新写 luaL_addlstring(&b, buff, strlen(buff)); }  } // 把栈上之前所有生成的TString通过luaV_concat(就是 .. 语法糖的合并函数)合并成一个TString,放到栈上,相当于返回值,供上层函数取用 luaL_pushresult(&b); return 1;}

    可以看出,string.format()会对输入进行解析,并将不同类型的数据格式转化为字符串,再存入buff中,之后的操作基本等同于table.concat,相比于table.concat,string.format不需要创建表,但是需要对输入数据进行解析转化,具体的应用场景还是需要参考实际需求。

四、string.rep(str,n):重复的对一个字符串做N次拼接

local start_time = os.clock()local result = string.rep("abc", 1000000)local end_time = os.clock()time = end_time - start_timeprint(time)

结果如下:可以看出使用string.rep来完成多次重复拼接的速度相对是很快的。

所以让我们来看下它的源码实现,如下:

static int str_rep (lua_State *L) {  // 获取字符串s  const char *s = luaL_checklstring(L, 1, &l); // 获取重复次数n  int n = luaL_checkint(L, 2); // 初始化一个buff,buff大小为8192  luaL_buffinit(L, &b); // 把字符串 s 写入到buff,重复写 n 次 // 当buff大小不够时的处理同string.format的实现。 while (n-- > 0)    luaL_addlstring(&b, s, l); // 获得最后的结果 luaL_pushresult(&b); return 1;}

    相比于string.format和table.concat,由于string.rep既不需要创建表,也不需要对输入数据进行解析,而是不断重复写入字符串,在写入数据达到buff上限时再生成一个Tstring,所以在重复拼接多个字符串时毫无疑问string.rep()的效率是最快的。

五、总结:

以上四种字符串拼接的方式各有优劣,总的来说遵循以下几条原则来实现字符串拼接即可:

(1)拼接大量重复字符串时使用string.rep()

(2)字符串简单且数量较少时使用运算符..

(3)对数据格式转换、对齐等格式有要求的应用string.format()

(4)子串数量庞大时应尽量采用table.concat()

参考文章:

https://blog.csdn.net/fengshenyun/article/details/89952494





以上是关于Lua字符串拼接的主要内容,如果未能解决你的问题,请参考以下文章

Lua中的字符串拼接浅析

Lua中string.format占位符的使用

lua 怎么通过空格来分割字符串

lua正则分割字符串求助

Lua 实现StringBuffer功能

Lua 实现StringBuffer功能