完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
Lua 字符串是内部化的不可变值:任何"修改"都会生成新字符串,短字符串还会进驻留池。理解这一点,两个高频坑就清晰了。
坑一:循环拼接。 s = s .. x 在循环里是 O(n²)——每轮都复制整个旧串。正确姿势是先收集后合并:
-- 差:1000 次全量复制
local s = ""
for _, name in ipairs(names) do s = s .. name .. "," end
-- 好:一次连接
local buf = {}
for _, name in ipairs(names) do buf[#buf + 1] = name end
local s = table.concat(buf, ",")
坑二:反复 tonumber/tostring 与格式化。 高频路径里 string.format("%d", x) 生成的临时串会加重 GC 负担,能复用就缓存格式化结果,尤其在 OnLoop 类触发器里。
Lua 的 table 同时是数组(连续部分)和哈希表(散列部分)。用 t[1]..t[n] 存数组时走连续内存,遍历最快;用字符串 key 或稀疏下标则走哈希,且稀疏数组会让 #t 长度语义变得不可靠——数组槽永远从 1 开始密集存放,别拿 table 当稀疏数组用。
建表时如果能预估容量,工程上推荐"构造器一次成型":local t = {a, b, c} 比先 local t = {} 再逐个 t[i]= 少数次 rehash。虽然单次差异微小,万次循环里就是可测量级。
ipairs 走数组部分、遇 nil 即停,最快;pairs 走哈希部分、顺序不保证,仅用于字典;已知长度时 for i=1,#t 与 ipairs 相当,但可以在倒序遍历、跳步遍历上更灵活。倒序删除元素务必倒着循环:
for i = #list, 1, -1 do
if list[i].dead then table.remove(list, i) end
end
正着边遍历边 remove 会跳元素,这是排行榜、怪物列表刷新逻辑里最经典的隐性 bug。
怀疑字符串/table 开销时,按顺序做三件事:collectgarbage("count") 前后对比定位内存增量;用 debug.sethook 做简易采样找热点函数;最后审查热点函数里的临时表与拼接。90% 的脚本性能问题都落在"循环拼接、循环建表、全局查找"这三件事上,改完它们,剩下的才值得动架构。
printusetime(play, on, off)
打印脚本总耗时(微秒),字符串拼接与 table 扩容的代价必须用实测数据说话
| 参数 | 类型 | 说明 |
|---|---|---|
| play | object | 玩家对象 |
| on | integer | 1=开始技术2=结束计时并打印耗时 |
| off | integer | 2=结束计时 |
printusetime(actor,1)
md5str(str)
MD5加密,字符串类工具函数里最常见的原生实现,性能基线参照
| 参数 | 类型 | 说明 |
|---|---|---|
| str | string | “需要加密的文本 |
tbl2jsonex(str)
表格转换成字符串,隐性开销的重灾区,序列化前后都值得量一次
| 参数 | 类型 | 说明 |
|---|---|---|
| str | table | 需要转表的table |
json2tbl(str, reslut)
字符串转换成表格,反序列化成本通常更高
| 参数 | 类型 | 说明 |
|---|---|---|
| str | string | 需要转表的json |
getliststring(path, str, result1, result2)
获取文本文件指定行的内容,从文件读字符串的既有口,注意行号从 0 开始
| 参数 | 类型 | 说明 |
|---|---|---|
| path | string | 文件路径(必填参数) |
| str | string | 指定行(必填参数) |
| result1 | string | 字符串(必填参数) |
| result2 | string | 返回值2当有内容穿在:时,会将:号后的字符分割返回(必填参数) |
release_print(msg)
打印消息到控制台,对比测试之间的输出口
| 参数 | 类型 | 说明 |
|---|---|---|
| msg | any | 打印内容 |