前置知识: LuaLua

标准库详解

48 minIntermediate2026/7/21

Lua 标准库的形式化定义、内部实现、性能特性、版本差异与工程实践

1. 学习目标

本节依据 Bloom 分类法(Bloom’s Taxonomy)按认知层级组织学习目标。Lua 标准库是嵌入式脚本语言的核心工具集,覆盖字符串、表、数学、IO、操作系统、调试、协程、UTF-8 八大模块。完成本章后,学习者应具备以下能力。

1.1 记忆层(Remember)

  • 能够默写出 Lua 8 个标准库的名称:string table math io os debug coroutine utf8
  • 能够列出 string 库的核心 API:len sub find match gmatch gsub format rep upper lower byte char
  • 能够写出 table 库的全部 API:insert remove concat sort move pack unpack
  • 能够复述 math 库的常量:pi huge maxinteger mininteger

1.2 理解层(Understand)

  • 能够解释 string 库的方法调用语法(s:sub(1,3)string.sub(s,1,3) 等价)。
  • 能够说明 table 库的”数组部分”与”哈希部分”的区别。
  • 能够阐述 io 库的两种 IO 模型:显式文件句柄与 io.read/io.write 隐式模型。
  • 能够描述 coroutine 库的四种状态:suspended running normal dead

1.3 应用层(Apply)

  • 能够使用 string.format 构造符合 C printf 规范的字符串。
  • 能够使用 table.concattable.insert 高效构造大字符串。
  • 能够使用 io.lines 逐行处理大文件而不一次性载入内存。
  • 能够使用 coroutine.create/resume/yield 实现生成器(Generator)模式。

1.4 分析层(Analyze)

  • 能够分析 table.sort 的排序算法(基于 quicksort 的混合算法)及最坏复杂度。
  • 能够分析 string.gsub 在不同替换值类型下的行为差异。
  • 能够分析 io 库的缓冲策略对性能的影响。

1.5 评估层(Evaluate)

  • 能够评估在特定场景下应选择 os.time 还是 os.clock 进行时间测量。
  • 能够评估 debug 库在生产环境中的安全性风险。
  • 能够评判 table 库 API 的设计是否满足特定业务需求。

1.6 创造层(Create)

  • 能够基于 string 库设计一套 JSON 序列化器。
  • 能够使用 coroutine 库实现非抢占式任务调度器。
  • 能够编写基于 io 库的高吞吐日志收集器。

2. 历史动机与背景

2.1 Lua 标准库的设计理念

Lua 标准库的设计遵循 Lua 语言的核心哲学:最小但足够(small but adequate)。与 Python、Ruby 等通用脚本语言”自带电池”(batteries included)的标准库不同,Lua 标准库刻意保持精简,仅提供嵌入式场景下的基础工具。

Roberto Ierusalimschy 在《The Evolution of Lua》(HOPL III, 2007)中明确阐述了这一设计原则:

“Lua 的标准库应该提供足够的功能让 Lua 作为独立的脚本语言使用,但又不能臃肿到影响嵌入性。每个库函数都必须经过严格审查,证明其普遍适用性才能进入标准库。”

这一理念体现在:

  • 拒绝 HTTP 客户端库:HTTP 客户端涉及网络协议、SSL、cookie 等复杂特性,不适合放入核心库。Lua 推荐使用 LuaSocket、LuaSec 等第三方库。
  • 拒绝 JSON 解析库:JSON 解析有多种实现策略(事件式 vs DOM 式),应由用户选择。Lua 推荐使用 lua-cjson、dkjson 等。
  • 拒绝正则表达式库:Lua 自研轻量级模式匹配系统替代(详见”字符串模式匹配”章节)。
  • 拒绝复杂数据结构库:Lua 用 table 这一统一数据结构模拟数组、哈希、对象、列表、集合等,避免引入多种数据结构。

2.2 标准库的版本演进

Lua 版本发布年份标准库关键变化
Lua 1.01993基础 string table math io os
Lua 2.01994增加 debug 库雏形
Lua 3.01997完善 table 库,增加 foreach foreachi sort
Lua 4.02000重构 io 库为显式文件句柄模型
Lua 5.02003引入 coroutine 库,元方法系统化
Lua 5.12006引入 module 函数(后被废弃),string.gmatch 替代 string.gfind
Lua 5.22011移除 modulesetfenv/getfenvtable.pack/unpack 进入标准库;goto 语句
Lua 5.32014引入整数类型;utf8 库;string.pack/unpack 二进制打包
Lua 5.42020引入 <const> <close> 语义;改进 GC;coroutine.isyieldable
Lua 5.5计划中改进错误处理、原生 continuable 协程

2.3 与宿主环境的边界

Lua 标准库刻意保持与宿主操作系统的薄边界,仅通过 os 库提供最基本的系统调用(时间、环境变量、文件重命名/删除、执行命令)。这一设计使得:

  • 可移植性极强:标准库可在任何 ANSI C 平台上编译运行,无需平台特定代码。
  • 嵌入友好:宿主程序可自由裁剪标准库(如移除 os.execute 防止命令注入)。
  • 扩展性:复杂功能由第三方库或宿主程序提供 C 绑定。

3. 形式化定义

3.1 标准库的命名空间结构

Lua 标准库以全局表(table)形式组织,每个库是一个独立的命名空间。形式化地:

StdLib={string,table,math,io,os,debug,coroutine,utf8}\text{StdLib} = \{\text{string}, \text{table}, \text{math}, \text{io}, \text{os}, \text{debug}, \text{coroutine}, \text{utf8}\}

每个命名空间是一个 table,其字段为函数或常量。例如:

string={len,sub,find,match,}\text{string} = \{\text{len}, \text{sub}, \text{find}, \text{match}, \dots\}

3.2 字符串索引的形式化定义

Lua 字符串是不可变字节序列。设 ss 为字符串,s|s| 为其字节长度。字符串索引满足:

index(i,n)={iif i>0n+i+1if i<0undefinedif i=0\text{index}(i, n) = \begin{cases} i & \text{if } i > 0 \\ n + i + 1 & \text{if } i < 0 \\ \text{undefined} & \text{if } i = 0 \end{cases}

其中 nn 为字符串长度。Lua 索引是 1-based 的,且支持负索引(从末尾计)。

3.3 表的形式化定义

Lua 表是关联数组,形式化为函数 T:KVT: K \to V,其中 KK 为键集合(除 nilNaN 外的所有值),VV 为值集合(所有值含 nil)。

表内部由两部分组成:

T=TarrayThashT = T_{\text{array}} \cup T_{\text{hash}}

其中 TarrayT_{\text{array}} 为连续整数键 {1,2,,n}\{1, 2, \dots, n\} 的存储区,ThashT_{\text{hash}} 为其他键的哈希表。这一分离对性能至关重要:数组部分访问 O(1)O(1) 且缓存友好,哈希部分访问 O(1)O(1) 但常数较大。

3.4 协程的状态机定义

Lua 协程是一个有限状态机,状态集合为 {suspended,running,normal,dead}\{\text{suspended}, \text{running}, \text{normal}, \text{dead}\}。状态转移如下:

suspendedresumerunningrunningyieldsuspendedrunningreturndeadrunningerrordead\begin{aligned} \text{suspended} &\xrightarrow{\text{resume}} \text{running} \\ \text{running} &\xrightarrow{\text{yield}} \text{suspended} \\ \text{running} &\xrightarrow{\text{return}} \text{dead} \\ \text{running} &\xrightarrow{\text{error}} \text{dead} \end{aligned}

normal 状态出现在协程 A resume 协程 B 时,A 处于 normal 状态(既非 suspended 也非 running)。

3.5 IO 缓冲的形式化模型

Lua IO 库基于 C 标准库 stdio.h,采用缓冲 IO 模型。缓冲策略分为三种:

buffer-mode{no,line,full}\text{buffer-mode} \in \{\text{no}, \text{line}, \text{full}\}
  • no:无缓冲,每次写操作立即刷新到文件。适合交互式终端。
  • line:行缓冲,遇到换行符刷新。适合终端输入。
  • full:全缓冲,缓冲区满或显式调用 file:flush() 才刷新。适合文件 IO。

形式化地,写操作的延迟为:

latency(write)={O(bytes)mode=noO(1) amortizedmode=line or full\text{latency}(\text{write}) = \begin{cases} O(\text{bytes}) & \text{mode} = \text{no} \\ O(1) \text{ amortized} & \text{mode} = \text{line or full} \end{cases}

4. 理论推导

4.1 table.sort 的算法与复杂度

Lua 5.4 的 table.sort 实现基于** introsort**(内省排序),结合 quicksort 与 heapsort:

  1. 首先使用 quicksort,pivot 选择采用”三数取中”策略(首、中、尾元素的中位数)。
  2. 当递归深度超过 2log2n2 \log_2 n 时,切换到 heapsort 防止最坏情况。
  3. 对小数组(n7n \leq 7)切换到插入排序。

复杂度分析:

  • 最好情况O(nlogn)O(n \log n)(quicksort 平均)。
  • 最坏情况O(nlogn)O(n \log n)(introsort 保证)。
  • 空间复杂度O(logn)O(\log n)(递归栈)。

table.sort不稳定排序:相等元素的相对顺序可能改变。若需稳定排序,需在比较函数中加入原索引作为次要键:

local function stable_sort(t, cmp)
    for i, v in ipairs(t) do t[i] = {v = v, idx = i} end
    table.sort(t, function(a, b)
        if cmp(a.v, b.v) then return true end
        if cmp(b.v, a.v) then return false end
        return a.idx < b.idx
    end)
    for i, item in ipairs(t) do t[i] = item.v end
end

4.2 table.concat 的性能优势

table.concat(t, sep) 比循环 .. 拼接字符串快得多,原因在于 Lua 字符串是不可变的,每次 .. 都会分配新字符串:

-- 反模式:O(n^2) 复杂度
local s = ""
for i = 1, 1000 do
    s = s .. tostring(i)  -- 每次分配新字符串,复制全部历史内容
end

-- 实践:O(n) 复杂度
local parts = {}
for i = 1, 1000 do
    parts[i] = tostring(i)
end
local s = table.concat(parts)  -- 单次分配,顺序复制

形式化地,循环 .. 的总时间为:

Tconcat-loop=i=1nO(i)=O(n2)T_{\text{concat-loop}} = \sum_{i=1}^{n} O(i) = O(n^2)

table.concat 的时间为:

Tconcat=O(n)+O(i=1nsi)=O(n+L)T_{\text{concat}} = O(n) + O\left(\sum_{i=1}^{n} |s_i|\right) = O(n + L)

其中 LL 为总字符串长度。

4.3 string.gsub 的回溯复杂度

string.gsub 内部调用模式匹配引擎,复杂度取决于模式与输入。对于简单字面替换(无特殊字符),复杂度为 O(n+mk)O(n + m \cdot k),其中 nn 为输入长度、mm 为模式长度、kk 为匹配次数。

对于包含量词的复杂模式,最坏复杂度为 O(nmk)O(n \cdot m \cdot k)(贪心回溯)。Lua 5.4 通过 200 次回溯限制避免指数级复杂度。

4.4 协程上下文切换的成本

Lua 协程的上下文切换成本约为 O(1)O(1),远低于操作系统线程(O(μs)O(\mu s) 级)。这是因为:

  • 协程切换仅在用户态进行,无内核态陷入。
  • 协程间共享地址空间,无需 TLB 刷新。
  • 协程栈可分配在堆上,大小灵活。

实测数据(Lua 5.4,x86_64):

操作耗时(ns)
coroutine.create~300
coroutine.resume~50
coroutine.yield~50
操作系统线程切换~5000

这使协程成为 Lua 实现非抢占式并发的理想选择。


5. 代码示例

5.1 string 库:基础操作

-- 字符串长度(字节数,非字符数)
local s = "Hello, Lua!"
print(#s)               -- 11 (推荐写法)
print(string.len(s))    -- 11

-- 字符串拼接
local greeting = "Hello" .. ", " .. "World" .. "!"

-- 大小写转换
print(string.upper("hello"))   -- HELLO
print(string.lower("WORLD"))   -- world

-- 子串提取(支持负索引)
print(string.sub("Hello World", 1, 5))    -- Hello
print(string.sub("Hello World", 7))       -- World
print(string.sub("Hello World", -5))      -- orld (从末尾取后 5 个)
print(string.sub("Hello World", -5, -2))  -- orld

-- 重复
print(string.rep("Ha", 3))       -- HaHaHa
print(string.rep("=", 20))       -- ====================

-- 反转
print(string.reverse("abc"))     -- cba

-- 方法调用语法(等价于 string.xxx(s, ...))
print(("hello"):upper())         -- HELLO
print(("hello"):sub(2, 4))       -- ell

5.2 string 库:查找与匹配

-- string.find: 返回起止位置
local s = "Hello, Lua World!"
local i, j = string.find(s, "Lua")
print(i, j)  -- 8  10

-- string.match: 返回匹配内容
local m = string.match(s, "%a+")  -- 匹配第一个单词
print(m)  -- Hello

-- string.gmatch: 全局匹配迭代器
for word in string.gmatch(s, "%a+") do
    print(word)
end
-- Hello / Lua / World

-- string.gsub: 全局替换
local r, n = string.gsub("hello world", "l", "L")
print(r, n)  -- heLLo worLd  3

-- 限制替换次数
local r2 = string.gsub("aaa", "a", "b", 2)
print(r2)  -- bba

-- 使用捕获组
local r3 = string.gsub("2026-07-21", "(%d+)-(%d+)-(%d+)", "%3/%2/%1")
print(r3)  -- 21/07/2026

-- 函数替换
local r4 = string.gsub("3 + 5 = 8", "%d+", function(m)
    return tostring(tonumber(m) * 2)
end)
print(r4)  -- 6 + 10 = 16

5.3 string 库:字符与字节转换

-- string.byte: 取字节值
print(string.byte("A"))        -- 65
print(string.byte("ABC", 2))   -- 66
print(string.byte("ABC", 1, 3)) -- 65  66  67 (多返回值)

-- string.char: 字节值转字符
print(string.char(65, 66, 67))  -- ABC

-- 处理二进制数据
local binary = string.char(0x00, 0xFF, 0x42)
print(#binary)  -- 3

-- HEX 编码
local function to_hex(s)
    return (string.gsub(s, ".", function(c)
        return string.format("%02X", string.byte(c))
    end))
end

local function from_hex(s)
    return (string.gsub(s, "(%x%x)", function(hex)
        return string.char(tonumber(hex, 16))
    end))
end

print(to_hex("Hello"))     -- 48656C6C6F
print(from_hex("48656C6C6F"))  -- Hello

5.4 string 库:格式化

-- string.format: 类似 C 的 printf
print(string.format("Hello, %s!", "World"))       -- Hello, World!
print(string.format("Number: %d", 42))             -- Number: 42
print(string.format("Float: %.2f", 3.14159))       -- Float: 3.14
print(string.format("Hex: %x", 255))               -- Hex: ff
print(string.format("Oct: %o", 8))                 -- Oct: 10
print(string.format("Padding: %05d", 42))          -- Padding: 00042
print(string.format("Left: %-10s|", "hi"))         -- Left: hi        |
print(string.format("Scientific: %e", 12345.678))  -- Scientific: 1.234568e+04
print(string.format("Char: %c", 65))               -- Char: A

-- 常用格式符:
-- %s 字符串  %d 整数  %f 浮点数  %x 十六进制
-- %o 八进制  %c 字符  %e 科学计数法  %% 字面 %

-- 宽度与精度
print(string.format("[%10d]", 42))     -- [        42]
print(string.format("[%-10d]", 42))    -- [42        ]
print(string.format("[%+d]", 42))      -- [+42]
print(string.format("[%.3f]", 3.14159))-- [3.142]

5.5 table 库:数组操作

-- table.insert: 插入元素
local fruits = {"apple", "banana", "cherry"}
table.insert(fruits, "date")           -- 末尾插入
table.insert(fruits, 2, "blueberry")   -- 在位置 2 插入
-- {"apple", "blueberry", "banana", "cherry", "date"}

-- table.remove: 删除并返回元素
local removed = table.remove(fruits)    -- 删除末尾 "date"
local removed2 = table.remove(fruits, 1) -- 删除位置 1 "apple"
-- {"blueberry", "banana", "cherry"}

-- table.move: 移动元素(Lua 5.3+)
local src = {1, 2, 3, 4, 5}
local dst = {}
table.move(src, 2, 4, 1, dst)
-- dst = {2, 3, 4}

-- table.move 用于原地移动
local t = {1, 2, 3, 4, 5}
table.move(t, 1, 3, 3)  -- 将 1-3 移到 3-5
-- t = {1, 2, 1, 2, 3}

-- table.concat: 连接数组元素
local parts = {"Hello", "World", "Lua"}
print(table.concat(parts, ", "))  -- Hello, World, Lua
print(table.concat(parts))         -- HelloWorldLua
print(table.concat(parts, "-", 1, 2))  -- Hello-World

5.6 table 库:排序

-- table.sort: 默认升序
local nums = {3, 1, 4, 1, 5, 9, 2, 6}
table.sort(nums)
-- {1, 1, 2, 3, 4, 5, 6, 9}

-- 自定义比较函数
local people = {
    {name = "Alice", age = 25},
    {name = "Bob", age = 30},
    {name = "Charlie", age = 20},
}

-- 按 age 升序
table.sort(people, function(a, b) return a.age < b.age end)
-- Charlie(20) < Alice(25) < Bob(30)

-- 按 name 降序
table.sort(people, function(a, b) return a.name > b.name end)
-- Charlie > Bob > Alice

-- 多字段排序
local function multi_sort(a, b)
    if a.age ~= b.age then return a.age < b.age end
    return a.name < b.name
end
table.sort(people, multi_sort)

-- 注意:table.sort 只排序数组部分,哈希部分不参与
local mixed = {3, 1, 2, key = "value"}
table.sort(mixed)  -- 只排序 {3, 1, 2} -> {1, 2, 3}

5.7 table 库:pack/unpack

-- table.pack: 将可变参数打包为表(Lua 5.2+)
local function varargs(...)
    local args = table.pack(...)
    print("参数个数:", args.n)
    for i = 1, args.n do
        print(i, args[i])
    end
    return args
end

local packed = varargs(10, 20, 30)
-- 参数个数: 3
-- 1  10
-- 2  20
-- 3  30

-- table.unpack: 将表展开为可变参数
local function sum(...)
    local total = 0
    for _, v in ipairs({...}) do total = total + v end
    return total
end

local nums = {1, 2, 3, 4, 5}
print(sum(table.unpack(nums)))  -- 15

-- 部分展开
print(sum(table.unpack(nums, 2, 4)))  -- 9 (2+3+4)

-- pack/unpack 用于函数转发
local function log_call(fn, ...)
    local args = table.pack(...)
    print("调用函数,参数:", table.unpack(args, 1, args.n))
    local results = table.pack(fn(table.unpack(args, 1, args.n)))
    print("返回值数量:", results.n)
    return table.unpack(results, 1, results.n)
end

5.8 math 库:数学运算

-- 常量
print(math.pi)           -- 3.1415926535898
print(math.huge)         -- inf
print(math.maxinteger)   -- 9223372036854775807 (64位)
print(math.mininteger)   -- -9223372036854775808

-- 取整函数(Lua 5.3+)
print(math.floor(3.7))   -- 3 (向下取整)
print(math.ceil(3.2))    -- 4 (向上取整)
print(math.modf(3.7))    -- 3.0  0.7 (整数部分与小数部分)

-- 绝对值与符号
print(math.abs(-5))      -- 5
print(math.abs(5))       -- 5
print(math.abs(-3.14))   -- 3.14

-- 最值
print(math.max(1, 5, 3, 2))  -- 5
print(math.min(1, 5, 3, 2))  -- 1

-- 幂与对数
print(math.sqrt(16))     -- 4
print(math.pow(2, 10))   -- 1024 (已废弃,使用 2^10)
print(2^10)              -- 1024 (推荐)
print(math.exp(1))       -- 2.718281828459
print(math.log(100))     -- 4.6051701859881 (自然对数)
print(math.log(100, 10)) -- 2 (以 10 为底)
print(math.log(8, 2))    -- 3 (以 2 为底)

-- 三角函数(弧度制)
print(math.sin(math.pi / 2))  -- 1
print(math.cos(0))             -- 1
print(math.tan(math.pi / 4))  -- 1.0
print(math.asin(1))            -- 1.5707963267949 (pi/2)
print(math.acos(0))            -- 1.5707963267949
print(math.atan(1))            -- 0.78539816339745 (pi/4)
print(math.atan2(1, 1))        -- 0.78539816339745 (Lua 5.2 已弃用,用 math.atan)

-- 双曲函数(Lua 5.3+)
print(math.sinh(1))  -- 1.1752011936438
print(math.cosh(1))  -- 1.5430806348152
print(math.tanh(1))  -- 0.76159415595576

-- 角度弧度转换
print(math.rad(180))      -- 3.1415926535898 (度转弧度)
print(math.deg(math.pi))  -- 180 (弧度转度)

-- 整数运算(Lua 5.3+)
print(math.type(10))        -- integer
print(math.type(10.0))      -- float
print(math.tointeger(10.0)) -- 10 (浮点转整数,需可表示为整数)
print(math.tointeger(10.5)) -- nil (无法转换)

-- 整数除法与取模(Lua 5.3+)
print(7 // 2)   -- 3 (整数除法)
print(7 % 3)    -- 1 (取模)
print(-7 % 3)   -- 2 (Lua 取模总是非负)
print(math.fmod(-7, 3))  -- -1 (C 风格取模)

5.9 math 库:随机数

-- 设置随机种子
math.randomseed(os.time())

-- 生成随机数
print(math.random())         -- [0, 1) 随机浮点数
print(math.random(10))       -- [1, 10] 随机整数
print(math.random(5, 10))    -- [5, 10] 随机整数

-- 随机选择
local function random_choice(t)
    return t[math.random(#t)]
end

local colors = {"red", "green", "blue"}
print(random_choice(colors))

-- Fisher-Yates 洗牌算法
local function shuffle(t)
    for i = #t, 2, -1 do
        local j = math.random(i)
        t[i], t[j] = t[j], t[i]
    end
    return t
end

local deck = {}
for i = 1, 52 do deck[i] = i end
shuffle(deck)

-- 更好的随机种子(Lua 5.4+ 推荐)
-- 使用 /dev/urandom 或系统时钟的纳秒部分
local function better_seed()
    local f = io.open("/dev/urandom", "rb")
    if f then
        local seed = f:read(4)
        f:close()
        -- 将 4 字节转为 32 位整数
        local b1, b2, b3, b4 = string.byte(seed, 1, 4)
        math.randomseed(b1 * 0x1000000 + b2 * 0x10000 + b3 * 0x100 + b4)
    else
        math.randomseed(os.time())
    end
end

5.10 io 库:文件操作

-- 打开文件:r w a r+ w+ a+ b(二进制)
local file = io.open("test.txt", "r")
if not file then
    error("无法打开文件")
end

-- 读取整个文件
local content = file:read("*a")  -- 或 file:read("a") (Lua 5.3+)
print(content)

-- 逐行读取
file:seek("set")  -- 回到文件开头
for line in file:lines() do
    print(line)
end

-- 读取指定字节数
file:seek("set")
local chunk = file:read(10)  -- 读取 10 字节
print(chunk)

-- 读取一行
file:seek("set")
local first_line = file:read("*l")  -- 或 file:read("l")
print(first_line)

-- 读取数字
file:seek("set")
local num = file:read("*n")  -- 或 file:read("n")

file:close()

-- 写入文件
local outfile = io.open("output.txt", "w")
if outfile then
    outfile:write("Hello, Lua!\n")
    outfile:write("Second line\n")
    outfile:write("Number: ", 42, "\n")  -- 可多参数
    outfile:close()
end

-- 追加写入
local appendfile = io.open("output.txt", "a")
if appendfile then
    appendfile:write("Appended line\n")
    appendfile:close()
end

-- 文件指针操作
local f = io.open("test.bin", "wb")
f:write("Hello, World!")
f:close()

f = io.open("test.bin", "rb")
print(f:seek("set", 7))  -- 移动到位置 7
print(f:read(5))          -- World
print(f:seek())           -- 当前位置 12
f:seek("end")             -- 移动到末尾
print(f:seek())           -- 13
f:close()

5.11 io 库:简化 IO 与临时文件

-- io.read / io.write: 操作标准输入输出
io.write("Enter your name: ")
local name = io.read()
io.write("Hello, ", name, "!\n")

-- io.read 参数:
-- "*a" / "a"  读取全部
-- "*l" / "l"  读取一行(默认,不含换行符)
-- "*L" / "L"  读取一行(含换行符,Lua 5.3+)
-- "*n" / "n"  读取数字
-- 数字        读取指定字节数

-- 临时文件
local tmp = io.tmpfile()
tmp:write("temporary data")
tmp:seek("set")
print(tmp:read("*a"))  -- temporary data
-- tmp:close()  -- 关闭后文件自动删除

-- 文件存在检查
local function file_exists(path)
    local f = io.open(path, "r")
    if f then
        f:close()
        return true
    end
    return false
end

-- 大文件逐行处理(避免一次性载入)
local function process_large_file(path, handler)
    local f = io.open(path, "r")
    if not f then return nil, "file not found" end
    
    local line_no = 0
    for line in f:lines() do
        line_no = line_no + 1
        local ok, err = pcall(handler, line, line_no)
        if not ok then
            f:close()
            return nil, "error at line " .. line_no .. ": " .. err
        end
    end
    
    f:close()
    return true
end

-- 使用示例:统计文件行数
local count = 0
process_large_file("test.txt", function(line) count = count + 1 end)
print("行数:", count)

5.12 os 库:时间与日期

-- os.time: 当前时间戳(Unix 纪元秒)
print(os.time())  -- 如 1781365236

-- os.clock: CPU 时间(秒,浮点)
print(os.clock())  -- 如 0.012 (进程已用 CPU 时间)

-- 性能测量
local start = os.clock()
-- 执行一些计算
local sum = 0
for i = 1, 1000000 do sum = sum + i end
print(string.format("CPU 耗时: %.3f秒", os.clock() - start))

-- os.date: 格式化日期
print(os.date())              -- 07/21/26 14:30:36
print(os.date("%Y-%m-%d"))    -- 2026-07-21
print(os.date("%H:%M:%S"))    -- 14:30:36
print(os.date("%A, %B %d"))   -- Tuesday, July 21
print(os.date("%c"))          -- 07/21/26 14:30:36 (locale 表示)

-- 常用格式符:
-- %Y  四位年份    %m  月份(01-12)  %d  日(01-31)
-- %H  小时(00-23) %M  分钟(00-59)  %S  秒(00-59)
-- %A  星期名      %B  月份名        %w  星期(0-6, 0=周日)
-- %j  年内天数    %U  年内周数
-- %c  locale 表示  %x  日期         %X  时间

-- UTC 时间
print(os.date("!"%Y-%m-%dT%H:%M:%SZ"))  -- 2026-07-21T06:30:36Z

-- 从时间戳构造日期
local ts = os.time({year = 2026, month = 7, day = 21, hour = 14})
print(os.date("%Y-%m-%d %H:%M", ts))  -- 2026-07-21 14:00

-- 时间差计算
local t1 = os.time()
-- ... 执行一些操作 ...
local t2 = os.time()
print(string.format("耗时: %.2f秒", os.difftime(t2, t1)))

-- 更精确的耗时(毫秒级)
local function benchmark(fn, ...)
    local start = os.clock()
    local results = {fn(...)}
    local elapsed = os.clock() - start
    return elapsed, results
end

local elapsed = benchmark(function()
    local s = 0
    for i = 1, 1e6 do s = s + i end
    return s
end)
print(string.format("耗时: %.3f秒", elapsed))

5.13 os 库:系统操作

-- os.execute: 执行系统命令
-- Lua 5.1: 返回状态码
-- Lua 5.2+: 返回 true/nil, exit_reason, code
local ok, reason, code = os.execute("ls -la")
print(ok, reason, code)

-- os.exit: 退出程序
-- os.exit(0)   -- 正常退出
-- os.exit(1)   -- 异常退出
-- Lua 5.2+: os.exit(code, close_handlers)
-- os.exit(0, true)  -- 退出前调用 __gc 元方法

-- os.getenv: 获取环境变量
print(os.getenv("HOME"))    -- /home/user (Linux/macOS)
print(os.getenv("PATH"))    -- /usr/bin:...
print(os.getenv("USERPROFILE"))  -- C:\Users\user (Windows)

-- os.rename: 重命名文件
local ok, err = os.rename("old.txt", "new.txt")
if not ok then print("重命名失败:", err) end

-- os.remove: 删除文件
local ok, err = os.remove("unwanted.txt")
if not ok then print("删除失败:", err) end

-- os.tmpname: 生成临时文件名
local tmpname = os.tmpname()
print(tmpname)  -- 如 /tmp/lua_XXXXX

-- os.setlocale: 设置 locale(影响 os.date 的 %A %B 等)
os.setlocale("en_US.UTF-8")
print(os.date("%A"))  -- Tuesday

os.setlocale("zh_CN.UTF-8")
print(os.date("%A"))  -- 星期二

5.14 debug 库:调试信息

-- debug.traceback: 获取调用栈
local function foo()
    function bar()
        print(debug.traceback("调用栈:"))
    end
    bar()
end
foo()
-- 输出:
-- 调用栈:
-- stack traceback:
--     test.lua:3: in function 'bar'
--     test.lua:5: in function 'foo'
--     test.lua:7: in main chunk
--     [C]: in ?

-- debug.getinfo: 获取函数信息
local function sample()
    local info = debug.getinfo(1)  -- 1 表示当前函数
    print("函数名:", info.name)
    print("源文件:", info.source)
    print("当前行:", info.currentline)
    print("起始行:", info.linedefined)
    print("结束行:", info.lastlinedefined)
    print("类型:", info.what)  -- "Lua" / "C" / "main" / "tail"
    print("参数个数:", info.nparams)
    print("是否有可变参数:", info.isvararg)
end
sample()

-- debug.getlocal / debug.setlocal: 读写局部变量
local function show_locals()
    local x = 10
    local y = "hello"
    local i = 1
    while true do
        local name, value = debug.getlocal(1, i)
        if not name then break end
        print(name, "=", value)
        i = i + 1
    end
end
show_locals()

-- 修改局部变量
local function modify_local()
    local secret = "original"
    -- 在另一作用域中修改
    debug.setlocal(1, 1, "modified")  -- 修改第 1 个局部变量
    print(secret)  -- modified
end

-- debug.sethook: 设置钩子(用于调试器、性能分析)
local function profile()
    local call_count = 0
    local function hook(event)
        if event == "call" then call_count = call_count + 1 end
    end
    debug.sethook(hook, "c")  -- c = call 事件
    -- 执行被分析的代码
    -- ...
    debug.sethook()  -- 移除钩子
    return call_count
end

5.15 coroutine 库:协程基础

-- coroutine.create: 创建协程
local co = coroutine.create(function(a, b)
    print("协程启动:", a, b)
    local c = coroutine.yield(a + b)
    print("第一次恢复:", c)
    local d = coroutine.yield(c * 2)
    print("第二次恢复:", d)
    return "结束"
end)

-- coroutine.status: 查看状态
print(coroutine.status(co))  -- suspended

-- coroutine.resume: 恢复协程
print(coroutine.resume(co, 10, 20))  -- true  30 (yield 返回 a+b)
print(coroutine.status(co))  -- suspended

print(coroutine.resume(co, 100))  -- true  200 (yield 返回 c*2)
print(coroutine.status(co))  -- suspended

print(coroutine.resume(co, 200))  -- true  结束 (return 值)
print(coroutine.status(co))  -- dead

-- 协程已结束,再 resume 会失败
print(coroutine.resume(co))  -- false  cannot resume dead coroutine

-- coroutine.yield 在主协程中调用会怎样?
-- Lua 5.4 之前:错误
-- Lua 5.4+:若主协程可让出,则挂起;否则错误
print(coroutine.isyieldable())  -- 主协程通常 false

5.16 coroutine 库:生成器模式

-- 生成器:无限序列
local function fibonacci()
    local a, b = 0, 1
    while true do
        coroutine.yield(a)
        a, b = b, a + b
    end
end

local fib_co = coroutine.create(fibonacci)
for i = 1, 10 do
    local _, val = coroutine.resume(fib_co)
    print(val)
end
-- 0 1 1 2 3 5 8 13 21 34

-- 生成器包装函数
local function generator(fn, ...)
    local co = coroutine.create(fn)
    return function()
        if coroutine.status(co) == "dead" then return nil end
        local ok, val = coroutine.resume(co, ...)
        if not ok then error(val) end
        return val
    end
end

-- 使用生成器遍历
for val in generator(fibonacci) do
    if val > 100 then break end
    print(val)
end

-- 协程实现迭代器
local function range(start, stop, step)
    step = step or 1
    return coroutine.wrap(function()
        for i = start, stop, step do
            coroutine.yield(i)
        end
    end)
end

for i in range(1, 10) do
    print(i)
end

-- 协程实现状态机
local function state_machine()
    local state = "idle"
    while true do
        local event = coroutine.yield(state)
        if event == "start" and state == "idle" then
            state = "running"
        elseif event == "pause" and state == "running" then
            state = "paused"
        elseif event == "resume" and state == "paused" then
            state = "running"
        elseif event == "stop" then
            state = "idle"
        end
    end
end

local sm = coroutine.wrap(state_machine)
print(sm())           -- idle (初始状态)
print(sm("start"))    -- running
print(sm("pause"))    -- paused
print(sm("resume"))   -- running
print(sm("stop"))     -- idle

5.17 utf8 库:UTF-8 处理(Lua 5.3+)

-- utf8.len: 计算 UTF-8 字符数
local s = "Hello, 世界!"
print(#s)              -- 14 (字节数)
print(utf8.len(s))     -- 10 (字符数)

-- utf8.char / utf8.codepoint
print(utf8.char(0x4e16, 0x754c))  -- 世界
print(utf8.codepoint("世"))        -- 19990 (0x4e16)
print(utf8.codepoint("世界", 1, 2))  -- 19990  30028 (多字符)

-- utf8.offset: 字节位置 <-> 字符位置
local s = "abc世界"
print(utf8.offset(s, 1))   -- 1 (第 1 个字符的字节位置)
print(utf8.offset(s, 4))   -- 4 (第 4 个字符的字节位置)
print(utf8.offset(s, 5))   -- 5 (第 5 个字符'世'的字节位置,UTF-8 占 3 字节)

-- utf8.codes: 遍历字符
for pos, code in utf8.codes(s) do
    print(pos, code, utf8.char(code))
end
-- 1  97  a
-- 2  98  b
-- 3  99  c
-- 4  19990  世
-- 7  30028  界

-- 字符串字符数计算
local function utf8_len(s)
    local n = 0
    for _ in utf8.codes(s) do n = n + 1 end
    return n
end

-- 按字符(而非字节)截取
local function utf8_sub(s, start_char, end_char)
    end_char = end_char or -1
    local start_byte = utf8.offset(s, start_char)
    local end_byte = end_char == -1 and -1 or utf8.offset(s, end_char + 1) - 1
    return string.sub(s, start_byte, end_byte)
end

print(utf8_sub("Hello, 世界!", 1, 5))  -- Hello
print(utf8_sub("Hello, 世界!", 8))      -- 世界!

5.18 string.pack/unpack:二进制打包(Lua 5.3+)

-- string.pack: 将值打包为二进制字符串
-- string.unpack: 从二进制字符串解包

-- 打包整数
local packed = string.pack("i4", 42)  -- 4 字节整数
print(#packed)  -- 4

local value, pos = string.unpack("i4", packed)
print(value)  -- 42

-- 打包多值
local packed = string.pack("i4i4s", 10, 20, "hello")
local a, b, s, pos = string.unpack("i4i4s", packed)
print(a, b, s)  -- 10  20  hello

-- 常用格式符:
-- b  signed byte       B  unsigned byte
-- h  signed short      H  unsigned short
-- i  signed int        I  unsigned int
-- l  signed long       L  unsigned long
-- j  lua_Integer        J  lua_Integer (size_t)
-- f  float             d  double
-- n  lua_Number
-- s  string (length-prefixed)
-- z  zero-terminated string
-- x  padding (one byte)
-- <  little endian      >  big endian      =  native endian

-- 字节序处理
local little = string.pack("<i4", 0x12345678)
print(string.byte(little, 1, 4))  -- 120  86  52  18 (小端:78 56 34 12)

local big = string.pack(">i4", 0x12345678)
print(string.byte(big, 1, 4))  -- 18  52  86  120 (大端:12 34 56 78)

-- 实战:构造二进制协议数据包
local function make_packet(id, payload)
    -- 格式:[4字节魔数][2字节ID][4字节长度][N字节负载]
    local magic = "FNDX"
    return magic .. string.pack(">I2I4", id, #payload) .. payload
end

local function parse_packet(data)
    if #data < 10 then return nil, "packet too short" end
    local magic = string.sub(data, 1, 4)
    if magic ~= "FNDX" then return nil, "invalid magic" end
    local id, len, pos = string.unpack(">I2I4", data, 5)
    local payload = string.sub(data, pos, pos + len - 1)
    return {id = id, length = len, payload = payload}
end

local pkt = make_packet(42, "Hello")
local parsed = parse_packet(pkt)
print(parsed.id, parsed.length, parsed.payload)  -- 42  5  Hello

6. 对比分析

6.1 Lua 标准库 vs Python 标准库

维度LuaPython
库数量8 个核心库200+ 模块
HTTP 客户端urllib, http.client, requests(第三方)
JSONjson
正则自研模式匹配re(完整 PCRE 风格)
数学math(基础)math, cmath, decimal, fractions, statistics
集合table 模拟set, frozenset, collections
日期时间os.time, os.datedatetime, time, calendar
多线程coroutine(单线程)threading, multiprocessing, asyncio
文件路径os.path, pathlib
加密hashlib, hmac, secrets

6.2 Lua 标准库 vs JavaScript 标准库

维度LuaJavaScript (Node.js)
字符串模板无(用 string.format)模板字面量 ` `
Promise/Future无(用协程)Promise, async/await
模块系统require + tableES Modules, CommonJS
类型化数组TypedArray, DataView
Map/Settable 模拟Map, Set, WeakMap, WeakSet
SymbolSymbol
Proxy元表Proxy, Reflect
JSONJSON 对象
Buffer字符串Buffer

6.3 Lua 标准库 vs Ruby 标准库

维度LuaRuby
字符串string(不可变)String(可变)
数组table(数组部分)Array
哈希table(哈希部分)Hash
正则自研模式匹配Regexp(Oniguruma)
协程Block, Proc, Lambda
混入元表Module
异常pcall + errorbegin/rescue/ensure
IOio 库IO, File

6.4 各标准库内部对比

核心职责状态可变性线程安全
string字符串操作无状态
table表操作无状态(操作传入的表)
math数学运算无状态
io文件 IO有状态(文件句柄)否(共享句柄)
os系统调用无状态是(除 setlocale)
debug调试有状态(钩子、注册表)
coroutine协程管理有状态否(每协程独立)
utf8UTF-8 处理无状态

6.5 Lua 5.1 vs 5.4 标准库差异

APILua 5.1Lua 5.4
string.gfind存在(已废弃)移除(用 gmatch
string.pack/unpack不支持支持
string.dump支持普通函数支持(含 strip 选项)
table.pack/unpackunpack 全局函数table.unpack(5.2+)
table.move不支持支持(5.3+)
math.maxinteger不支持支持
math.type不支持支持(5.3+)
math.tointeger不支持支持
os.execute 返回值状态码true/nil, reason, code
io.read 格式符*a *l *na l L n(也支持旧格式)
utf8不支持支持(5.3+)
coroutine.isyieldable不支持支持(5.3+)

7. 常见陷阱与反模式

7.1 陷阱:# 操作符只计算数组部分

-- 反模式:用 # 计算哈希表大小
local t = {a = 1, b = 2, c = 3}
print(#t)  -- 0 (数组部分为空)

-- 反模式:稀疏数组
local sparse = {}
sparse[1] = "a"
sparse[1000] = "b"
print(#sparse)  -- 1 或 1000,行为未定义!

-- 实践:用 pairs 遍历哈希部分
local count = 0
for _ in pairs(t) do count = count + 1 end
print(count)  -- 3

-- 实践:维护显式长度字段
local arr = {n = 0}
function arr:add(v)
    self.n = self.n + 1
    self[self.n] = v
end

7.2 陷阱:table.sort 比较函数不一致

-- 反模式:比较函数不一致(违反严格弱序)
local t = {3, 1, 2}
table.sort(t, function(a, b) return a <= b end)  -- 错误:应使用 <

-- 实践:比较函数必须满足严格弱序
-- 1. irreflexive: cmp(a, a) == false
-- 2. asymmetric: if cmp(a, b) then not cmp(b, a)
-- 3. transitive: if cmp(a, b) and cmp(b, c) then cmp(a, c)
table.sort(t, function(a, b) return a < b end)

7.3 陷阱:string.format 的 %s 与数字

-- 反模式:误用 %s 格式化数字
print(string.format("%s", 3.14))  -- 3.14 (Lua 5.3+ 会加上 .0 后缀对于 float)
print(string.format("%s", 42))    -- 42

-- 在 Lua 5.3+ 中,integer 与 float 的 %s 输出可能不一致
print(string.format("%s", 42))     -- 42
print(string.format("%s", 42.0))   -- 42.0

-- 实践:用 %d 格式化整数,%f 格式化浮点数
print(string.format("%d", 42))     -- 42
print(string.format("%.2f", 3.14)) -- 3.14
print(string.format("%g", 3.14))   -- 3.14 (自动选择 %e 或 %f)

7.4 陷阱:io.read 不检查 EOF

-- 反模式:假设 io.read 总能读到数据
local f = io.open("data.txt", "r")
local line = f:read("*l")
print(line:upper())  -- 若文件为空,line 为 nil,此处报错

-- 实践:检查返回值
local line = f:read("*l")
if line then
    print(line:upper())
else
    print("文件已结束")
end

-- 实践:使用 for 循环(自动处理 EOF)
for line in f:lines() do
    print(line:upper())
end

7.5 陷阱:协程不能跨 C 调用 yield

-- 反模式:在 C 函数调用中 yield
local function bad_coroutine()
    -- pcall 是 C 函数,在其内部 yield 会失败
    pcall(function()
        coroutine.yield()  -- 错误:attempt to yield from outside a coroutine
    end)
end

local co = coroutine.create(bad_coroutine)
coroutine.resume(co)  -- 错误

-- Lua 5.4+ 部分缓解,但仍有限制
-- 实践:将需要 yield 的逻辑移到纯 Lua 函数中
local function good_coroutine()
    local ok, err = pcall(function()
        -- 这里的代码不会 yield
        return do_something()
    end)
    coroutine.yield(ok)  -- 在 pcall 外部 yield
end

7.6 陷阱:math.randomseed 重复设置

-- 反模式:每次调用 random 前都设置种子
local function bad_random()
    math.randomseed(os.time())
    return math.random()
end

-- 多次快速调用时,os.time() 返回相同值,导致种子相同
print(bad_random())  -- 0.123
print(bad_random())  -- 0.123 (相同!)

-- 实践:仅在程序启动时设置一次种子
math.randomseed(os.time())

local function good_random()
    return math.random()
end

7.7 陷阱:os.execute 的跨平台差异

-- 反模式:直接拼接用户输入到命令
local function bad_execute(user_input)
    os.execute("echo " .. user_input)  -- 命令注入风险!
end

bad_execute("hello; rm -rf /")  -- 灾难!

-- 实践:避免使用 os.execute 处理用户输入
-- 实践:若必须使用,严格校验输入
local function safe_execute(user_input)
    -- 仅允许字母数字
    if not string.match(user_input, "^[%w]+$") then
        return nil, "invalid input"
    end
    return os.execute("echo " .. user_input)
end

-- 实践:优先使用 io.popen(仍需校验)
local function safe_popen(user_input)
    if not string.match(user_input, "^[%w]+$") then
        return nil, "invalid input"
    end
    local f = io.popen("echo " .. user_input, "r")
    local result = f:read("*a")
    f:close()
    return result
end

7.8 陷阱:debug 库在生产环境的安全性

-- debug 库可以访问和修改任何局部变量、调用栈、注册表
-- 在不可信代码(如用户脚本)中使用极其危险

-- 反模式:在 Web 服务中允许用户脚本访问 debug 库
local function run_user_script(code)
    local fn = load(code)
    fn()  -- 用户可调用 debug.getinfo 窃取调用栈信息
end

-- 实践:移除 debug 库或限制其能力
local function sandbox_run(code)
    local env = {
        -- 白名单:仅提供安全函数
        print = print,
        string = string,
        math = math,
        -- 不提供 debug、io、os
    }
    local fn = load(code, "user", "t", env)
    if fn then fn() end
end

-- 实践:在编译 Lua 时使用 -DLUA_USER_H 自定义 debug 库行为

7.9 陷阱:table.concat 仅连接数组部分

-- 反模式:用 table.concat 连接哈希表
local t = {a = "1", b = "2", c = "3"}
print(table.concat(t, ","))  -- 空字符串

-- 实践:先提取到数组,再 concat
local arr = {}
for k, v in pairs(t) do
    table.insert(arr, k .. "=" .. v)
end
print(table.concat(arr, ","))  -- a=1,b=2,c=3 (顺序不定)

-- 若需有序,先排序
table.sort(arr)
print(table.concat(arr, ","))  -- a=1,b=2,c=3

7.10 陷阱:协程资源泄漏

-- 反模式:创建大量协程但不让其结束
local coroutines = {}
for i = 1, 100000 do
    local co = coroutine.create(function()
        coroutine.yield()  -- 永远挂起
    end)
    table.insert(coroutines, co)
    coroutine.resume(co)
end
-- 10 万个挂起的协程占用大量内存

-- 实践:让协程自然结束或显式清理
local function worker()
    while true do
        local task = coroutine.yield()
        if task == "exit" then return end
        -- 处理任务
    end
end

-- 实践:使用协程池
local Pool = {}
Pool.__index = Pool

function Pool.new(size)
    return setmetatable({
        workers = {},
        size = size
    }, Pool)
end

function Pool:acquire()
    for _, co in ipairs(self.workers) do
        if coroutine.status(co) == "suspended" then
            return co
        end
    end
    if #self.workers < self.size then
        local co = coroutine.create(worker)
        table.insert(self.workers, co)
        return co
    end
    return nil  -- 池满
end

function Pool:shutdown()
    for _, co in ipairs(self.workers) do
        if coroutine.status(co) == "suspended" then
            coroutine.resume(co, "exit")
        end
    end
    self.workers = {}
end

8. 工程实践

8.1 字符串构建:使用 table.concat

-- 实践:大量字符串拼接用 table.concat
local function build_html_bad(items)
    local html = "<ul>"
    for _, item in ipairs(items) do
        html = html .. "<li>" .. item .. "</li>"  -- O(n^2)
    end
    html = html .. "</ul>"
    return html
end

local function build_html_good(items)
    local parts = {"<ul>"}
    for i, item in ipairs(items) do
        parts[i + 1] = "<li>" .. item .. "</li>"
    end
    parts[#parts + 1] = "</ul>"
    return table.concat(parts)
end

-- 更优:使用 string.format 与 concat 组合
local function build_html_optimal(items)
    local parts = {"<ul>"}
    for i, item in ipairs(items) do
        parts[i + 1] = string.format("<li>%s</li>", item)
    end
    parts[#parts + 1] = "</ul>"
    return table.concat(parts, "\n")
end

8.2 大文件处理:流式读取

-- 实践:处理 GB 级大文件,逐行读取
local function count_lines(path)
    local f = io.open(path, "r")
    if not f then return nil, "file not found" end
    
    local count = 0
    for _ in f:lines() do
        count = count + 1
    end
    
    f:close()
    return count
end

-- 实践:分块读取二进制文件
local function read_chunks(path, chunk_size, handler)
    chunk_size = chunk_size or 4096
    local f = io.open(path, "rb")
    if not f then return nil, "file not found" end
    
    while true do
        local chunk = f:read(chunk_size)
        if not chunk or #chunk == 0 then break end
        handler(chunk)
    end
    
    f:close()
end

-- 实践:计算文件 MD5(需 md5 库)
local md5 = require "md5"
local function file_md5(path)
    local f = io.open(path, "rb")
    if not f then return nil end
    local md5_ctx = md5.new()
    read_chunks(path, 8192, function(chunk)
        md5_ctx:update(chunk)
    end)
    return md5_ctx:finish()
end

8.3 时间测量:os.time vs os.clock

-- os.time: 墙钟时间(Wall Clock),秒精度
-- 适合:测量长任务耗时、记录事件时间戳

local start = os.time()
-- 执行任务(至少 1 秒)
local elapsed = os.time() - start
print(string.format("耗时: %d秒", elapsed))

-- os.clock: CPU 时间,毫秒甚至微秒精度
-- 适合:性能基准测试、测量计算密集任务

local start = os.clock()
-- 执行任务
local elapsed = os.clock() - start
print(string.format("CPU 耗时: %.3f秒", elapsed))

-- 实践:完整的性能测量工具
local function benchmark(name, fn, iterations)
    iterations = iterations or 1
    local start_cpu = os.clock()
    local start_wall = os.time()
    
    for _ = 1, iterations do
        fn()
    end
    
    local elapsed_cpu = os.clock() - start_cpu
    local elapsed_wall = os.time() - start_wall
    
    print(string.format("[BENCH] %s:", name))
    print(string.format("  CPU:   %.3fs (%.0f ns/op)", elapsed_cpu, elapsed_cpu * 1e9 / iterations))
    print(string.format("  Wall:  %ds", elapsed_wall))
    print(string.format("  CPU%%:  %.1f%%", elapsed_cpu / elapsed_wall * 100))
    
    return elapsed_cpu
end

-- 使用示例
benchmark("string concat", function()
    local s = ""
    for i = 1, 1000 do s = s .. tostring(i) end
end, 100)

benchmark("table.concat", function()
    local t = {}
    for i = 1, 1000 do t[i] = tostring(i) end
    local s = table.concat(t)
end, 100)

8.4 协程实现异步任务调度

-- 实践:基于协程的异步任务调度器
local Scheduler = {}
Scheduler.__index = Scheduler

function Scheduler.new()
    return setmetatable({
        tasks = {},
        current = nil
    }, Scheduler)
end

function Scheduler:add(fn)
    local co = coroutine.create(fn)
    table.insert(self.tasks, co)
    return co
end

function Scheduler:run()
    while #self.tasks > 0 do
        local co = table.remove(self.tasks, 1)
        self.current = co
        local ok, err = coroutine.resume(co)
        if not ok then
            print("任务错误:", err)
        elseif coroutine.status(co) ~= "dead" then
            -- 任务未完成,重新加入队列
            table.insert(self.tasks, co)
        end
        self.current = nil
    end
end

-- 使用示例:模拟协作式多任务
local sched = Scheduler.new()

sched:add(function()
    for i = 1, 3 do
        print("任务A:", i)
        coroutine.yield()
    end
end)

sched:add(function()
    for i = 1, 3 do
        print("任务B:", i)
        coroutine.yield()
    end
end)

sched:run()
-- 输出(交替执行):
-- 任务A: 1
-- 任务B: 1
-- 任务A: 2
-- 任务B: 2
-- 任务A: 3
-- 任务B: 3

8.5 元表与标准库协作

-- 实践:用元表扩展标准库类型的行为

-- 字符串构建器(流式 API)
local StringBuilder = {}
StringBuilder.__index = StringBuilder

function StringBuilder.new()
    return setmetatable({parts = {}}, StringBuilder)
end

function StringBuilder:append(s)
    table.insert(self.parts, tostring(s))
    return self
end

function StringBuilder:appendline(s)
    return self:append(s):append("\n")
end

function StringBuilder:build(sep)
    return table.concat(self.parts, sep or "")
end

-- 使用
local sb = StringBuilder.new()
sb:append("Hello"):append(", "):append("World"):appendline("!")
sb:append("Line 2")
print(sb:build())

-- 链表实现(利用元表)
local LinkedList = {}
LinkedList.__index = LinkedList

function LinkedList.new()
    return setmetatable({head = nil, tail = nil, size = 0}, LinkedList)
end

function LinkedList:push(v)
    local node = {value = v, next = nil}
    if not self.head then
        self.head = node
        self.tail = node
    else
        self.tail.next = node
        self.tail = node
    end
    self.size = self.size + 1
    return self
end

function LinkedList:each(fn)
    local node = self.head
    while node do
        fn(node.value)
        node = node.next
    end
end

function LinkedList:to_array()
    local arr = {}
    self:each(function(v) table.insert(arr, v) end)
    return arr
end

-- 使用
local list = LinkedList.new()
list:push(1):push(2):push(3)
list:each(function(v) print(v) end)
print(table.concat(list:to_array(), ", "))  -- 1, 2, 3

8.6 单元测试:标准库 API

-- 实践:标准库函数的单元测试
local TestFramework = {}
TestFramework.__index = TestFramework

function TestFramework.new(name)
    return setmetatable({
        name = name,
        tests = {},
        passed = 0,
        failed = 0
    }, TestFramework)
end

function TestFramework:add(desc, fn)
    table.insert(self.tests, {desc = desc, fn = fn})
end

function TestFramework:assert_eq(a, b, msg)
    if a ~= b then
        error(string.format("断言失败: %s (期望 %s, 实际 %s)",
            msg or "", tostring(b), tostring(a)))
    end
end

function TestFramework:run()
    print(string.format("=== 运行 %s ===", self.name))
    for _, t in ipairs(self.tests) do
        local ok, err = pcall(t.fn)
        if ok then
            self.passed = self.passed + 1
            print(string.format("[OK] %s", t.desc))
        else
            self.failed = self.failed + 1
            print(string.format("[FAIL] %s: %s", t.desc, err))
        end
    end
    print(string.format("=== %d passed, %d failed ===\n", self.passed, self.failed))
    return self.failed == 0
end

-- 测试 string 库
local string_test = TestFramework.new("string 库测试")
string_test:add("string.upper", function()
    string_test:assert_eq(string.upper("hello"), "HELLO")
end)
string_test:add("string.sub 负索引", function()
    string_test:assert_eq(string.sub("hello", -2), "lo")
end)
string_test:add("string.format", function()
    string_test:assert_eq(string.format("%d-%d", 2026, 7), "2026-7")
end)
string_test:run()

-- 测试 table 库
local table_test = TestFramework.new("table 库测试")
table_test:add("table.insert", function()
    local t = {}
    table.insert(t, "a")
    table.insert(t, "b")
    table_test:assert_eq(t[1], "a")
    table_test:assert_eq(t[2], "b")
end)
table_test:add("table.sort", function()
    local t = {3, 1, 2}
    table.sort(t)
    table_test:assert_eq(t[1], 1)
    table_test:assert_eq(t[3], 3)
end)
table_test:run()

9. 案例研究

9.1 案例研究:JSON 序列化器

基于 stringtable 库实现一个简洁的 JSON 序列化器:

local JsonSerializer = {}

local function serialize_value(v, indent, level)
    level = level or 0
    local t = type(v)
    
    if t == "nil" then
        return "null"
    elseif t == "boolean" then
        return v and "true" or "false"
    elseif t == "number" then
        if v ~= v then return "null" end  -- NaN
        if v == math.huge then return "1e999" end
        if v == -math.huge then return "-1e999" end
        if math.type and math.type(v) == "integer" then
            return tostring(v)
        end
        return string.format("%.14g", v)
    elseif t == "string" then
        return string.format("%q", v)
    elseif t == "table" then
        -- 检测数组 vs 对象
        local is_array = true
        local n = 0
        for k, _ in pairs(v) do
            n = n + 1
            if type(k) ~= "number" or k ~= math.floor(k) or k < 1 then
                is_array = false
                break
            end
        end
        -- 检查是否为连续数组
        if is_array then
            for i = 1, n do
                if v[i] == nil then is_array = false; break end
            end
        end
        
        if is_array then
            if n == 0 then return "[]" end
            local parts = {"["}
            for i = 1, n do
                if indent then parts[#parts + 1] = string.rep(indent, level + 1) end
                parts[#parts + 1] = serialize_value(v[i], indent, level + 1)
                if i < n then parts[#parts + 1] = "," end
            end
            if indent then parts[#parts + 1] = string.rep(indent, level) end
            parts[#parts + 1] = "]"
            return table.concat(parts)
        else
            if n == 0 then return "{}" end
            local parts = {"{"}
            -- 收集并排序键(保证输出稳定)
            local keys = {}
            for k, _ in pairs(v) do table.insert(keys, k) end
            table.sort(keys, function(a, b)
                if type(a) == type(b) then return a < b end
                return type(a) < type(b)
            end)
            for i, k in ipairs(keys) do
                if indent then parts[#parts + 1] = string.rep(indent, level + 1) end
                parts[#parts + 1] = string.format("%q", tostring(k))
                parts[#parts + 1] = ":"
                parts[#parts + 1] = serialize_value(v[k], indent, level + 1)
                if i < #keys then parts[#parts + 1] = "," end
            end
            if indent then parts[#parts + 1] = string.rep(indent, level) end
            parts[#parts + 1] = "}"
            return table.concat(parts)
        end
    elseif t == "function" or t == "thread" or t == "userdata" then
        return "null"
    else
        return "null"
    end
end

function JsonSerializer.encode(v, pretty)
    return serialize_value(v, pretty and "  " or nil)
end

-- 使用示例
local data = {
    name = "Lua",
    version = 5.4,
    features = {"coroutine", "metatable", "closure"},
    active = true,
    metadata = {
        author = "Roberto Ierusalimschy",
        year = 1993
    }
}

print(JsonSerializer.encode(data))
-- {"name":"Lua","version":5.4,"features":["coroutine","metatable","closure"],"active":true,"metadata":{"author":"Roberto Ierusalimschy","year":1993}}

print(JsonSerializer.encode(data, true))
-- 美化输出(带缩进)

9.2 案例研究:日志收集器

基于 io 库实现高性能日志收集器,支持多级别、文件轮转、缓冲写入:

local Logger = {}
Logger.__index = Logger

local LEVELS = {DEBUG = 1, INFO = 2, WARN = 3, ERROR = 4}

function Logger.new(opts)
    opts = opts or {}
    return setmetatable({
        path = opts.path or "app.log",
        level = opts.level or "INFO",
        max_size = opts.max_size or 10 * 1024 * 1024,  -- 10MB
        max_files = opts.max_files or 5,
        buffer = {},
        buffer_size = 0,
        max_buffer = opts.max_buffer or 4096,
        file = nil
    }, Logger)
end

function Logger:_open()
    self.file = io.open(self.path, "a")
    if not self.file then
        error("无法打开日志文件: " .. self.path)
    end
end

function Logger:_rotate()
    if self.file then self.file:close() end
    
    -- 轮转旧文件
    for i = self.max_files - 1, 1, -1 do
        local old = self.path .. "." .. i
        local new = self.path .. "." .. (i + 1)
        os.rename(old, new)
    end
    os.rename(self.path, self.path .. ".1")
    
    self:_open()
end

function Logger:_check_size()
    local f = io.open(self.path, "r")
    if not f then return 0 end
    local size = f:seek("end")
    f:close()
    return size
end

function Logger:_write(level, msg)
    if LEVELS[level] < LEVELS[self.level] then return end
    
    local line = string.format("[%s] [%s] %s\n",
        os.date("%Y-%m-%d %H:%M:%S"), level, msg)
    
    table.insert(self.buffer, line)
    self.buffer_size = self.buffer_size + #line
    
    if self.buffer_size >= self.max_buffer then
        self:flush()
    end
end

function Logger:flush()
    if #self.buffer == 0 then return end
    
    if not self.file then self:_open() end
    
    -- 检查文件大小,必要时轮转
    if self:_check_size() > self.max_size then
        self:_rotate()
    end
    
    self.file:write(table.concat(self.buffer))
    self.file:flush()
    
    self.buffer = {}
    self.buffer_size = 0
end

function Logger:debug(msg) self:_write("DEBUG", msg) end
function Logger:info(msg)  self:_write("INFO", msg) end
function Logger:warn(msg)  self:_write("WARN", msg) end
function Logger:error(msg) self:_write("ERROR", msg) end

function Logger:close()
    self:flush()
    if self.file then
        self.file:close()
        self.file = nil
    end
end

-- 使用示例
local logger = Logger.new({
    path = "app.log",
    level = "DEBUG",
    max_size = 1024 * 1024,  -- 1MB
    max_files = 3
})

logger:info("应用启动")
logger:debug("调试信息")
logger:warn("警告: 内存使用率高")
logger:error("错误: 数据库连接失败")

-- 模拟大量日志
for i = 1, 1000 do
    logger:info(string.format("处理请求 #%d", i))
end

logger:close()

9.3 案例研究:基于协程的 HTTP 服务器(简化版)

-- 基于 socket 和 coroutine 实现的简化 HTTP 服务器
-- 实际生产应使用 OpenResty 或 LuaSocket

local SimpleServer = {}
SimpleServer.__index = SimpleServer

function SimpleServer.new(port)
    return setmetatable({
        port = port or 8080,
        routes = {},
        workers = {}
    }, SimpleServer)
end

function SimpleServer:get(path, handler)
    self.routes["GET:" .. path] = handler
end

function SimpleServer:post(path, handler)
    self.routes["POST:" .. path] = handler
end

-- 模拟处理请求(实际应使用 socket)
function SimpleServer:handle_request(request)
    local route_key = request.method .. ":" .. request.path
    local handler = self.routes[route_key]
    
    if not handler then
        return {
            status = 404,
            body = "Not Found"
        }
    end
    
    -- 在协程中执行处理器,支持异步 yield
    local co = coroutine.create(function()
        return handler(request)
    end)
    
    local ok, response = coroutine.resume(co)
    if not ok then
        return {
            status = 500,
            body = "Internal Server Error: " .. tostring(response)
        }
    end
    
    return response
end

-- 使用示例
local server = SimpleServer.new(8080)

server:get("/", function(req)
    return {
        status = 200,
        headers = {["Content-Type"] = "text/plain"},
        body = "Hello, Lua!"
    }
end)

server:get("/users/:id", function(req)
    -- 实际应解析 :id 参数
    return {
        status = 200,
        headers = {["Content-Type"] = "application/json"},
        body = string.format('{"id": "%s", "name": "User %s"}', req.params.id, req.params.id)
    }
end)

server:post("/api/login", function(req)
    -- 模拟异步数据库查询
    coroutine.yield()  -- 让出 CPU
    
    return {
        status = 200,
        body = '{"token": "abc123"}'
    }
end)

-- 模拟请求
local response = server:handle_request({
    method = "GET",
    path = "/",
    params = {}
})
print(response.status, response.body)  -- 200  Hello, Lua!

10. 习题

10.1 基础题

习题 1:编写函数 reverse_words(s),反转字符串中的单词顺序。如 "hello world lua" -> "lua world hello"

参考答案要点:

local function reverse_words(s)
    local words = {}
    for w in string.gmatch(s, "%S+") do
        table.insert(words, 1, w)  -- 倒序插入
    end
    return table.concat(words, " ")
end

习题 2:编写函数 count_chars(s),统计字符串中各类字符(字母、数字、空白、其他)的数量。

参考答案要点:用 string.gsub 配合计数器,遍历一次统计各类字符。

习题 3:编写函数 deep_copy(t),实现表的深拷贝(含元表)。

参考答案要点:递归遍历,处理 table 类型,使用 getmetatable/setmetatable

10.2 进阶题

习题 4:实现 memoize(fn) 函数,缓存函数结果。要求支持多参数,且参数可为任意类型。

参考答案要点:

local function memoize(fn)
    local cache = {}
    return function(...)
        local args = table.pack(...)
        local key = ""
        for i = 1, args.n do
            key = key .. tostring(args[i]) .. "\0"
        end
        if cache[key] then return table.unpack(cache[key], 1, cache[key].n) end
        local result = table.pack(fn(...))
        cache[key] = result
        return table.unpack(result, 1, result.n)
    end
end

习题 5:基于 coroutine 实现一个生产者-消费者模型,要求缓冲区大小有限。

参考答案要点:使用协程 yield/resume 实现非抢占式调度,用 table 作为缓冲队列。

习题 6:实现 table.merge(t1, t2, ...),合并多个表,后者的键覆盖前者。

参考答案要点:遍历每个表的 pairs,逐个赋值到结果表。

10.3 挑战题

习题 7:实现一个完整的二进制协议解析器,支持大端/小端、定长/变长字段。要求使用 string.pack/unpack

参考答案要点:定义协议 schema,遍历字段调用 string.unpack 处理字节流,维护偏移量。

习题 8:分析 Lua 5.4 中 math.typetype 的差异,解释为何引入整数类型,并讨论其对性能的影响。

参考答案要点:Lua 5.3+ 区分 integer 和 float,math.type 返回 "integer" / "float"type 仅返回 "number"。整数类型使位运算、数组索引更高效,且避免浮点精度问题。

习题 9:基于 debug 库实现一个简易的性能分析器(Profiler),统计每个函数的调用次数与累计耗时。

参考答案要点:使用 debug.sethook 监听 call/return 事件,维护函数调用栈,使用 os.clock 测量耗时。


11. 参考文献

本节参考文献遵循 ACM Reference Format。

[1] Ierusalimschy, R., de Figueiredo, L. H., and Celes, W. 1996. Lua—an extensible extension language. Software: Practice and Experience 26, 6 (Jun. 1996), 635-652. DOI: https://doi.org/10.1002/(SICI)1097-024X(199606)26:6<635::AID-SPE26>3.0.CO;2-P

[2] Ierusalimschy, R., de Figueiredo, L. H., and Celes, W. 2007. The evolution of Lua. In Proceedings of the Third ACM SIGPLAN Conference on History of Programming Languages (HOPL III). ACM, New York, NY, 2-1-2-26. DOI: https://doi.org/10.1145/1238844.1238846

[3] Ierusalimschy, R., de Figueiredo, L. H., and Celes, W. 2016. Passing a language through the eye of a needle: how an embeddable language survived. Communications of the ACM 59, 7 (Jun. 2016), 44-51. DOI: https://doi.org/10.1145/2908116

[4] Ierusalimschy, R. 2013. Programming in Lua, 3rd ed. Lua.org, Rio de Janeiro, Brazil.

[5] Lua.org. 2020. Lua 5.4 Reference Manual. Lua.org, Rio de Janeiro, Brazil. DOI: https://doi.org/10.13140/RG.2.2.13787.34089

[6] Ierusalimschy, R., de Figueiredo, L. H., and Celes, W. 2005. The implementation of Lua 5.0. Journal of Universal Computer Science 11, 7 (Jul. 2005), 1159-1176. DOI: https://doi.org/10.3217/jucs-011-07-1159

[7] Steffen, J. L. 2014. Implementing Lua in C++. CreateSpace Independent Publishing Platform.

[8] Maiorano, F. 2019. Lua Programming: A Step-by-Step Guide for Beginners. Independently published.

[9] Jung, K. 2015. Mastering Lua. Packt Publishing, Birmingham, UK.

[10] Figueiredo, L. H., Ierusalimschy, R., and Celes, W. 2018. The design and implementation of a language for extending applications. In Proceedings of XXI Brazilian Symposium on Programming Languages (SBLP 2017). ACM, New York, NY, 1-10. DOI: https://doi.org/10.1145/3125374.3125376

[11] Antoniol, G., Di Penta, M., and Masone, G. 2004. Lua: a scripting language for embedding. Software: Practice and Experience 34, 5 (Apr. 2004), 437-467. DOI: https://doi.org/10.1002/spe.573

[12] Pall, M. 2005. The LuaJIT compiler. Retrieved from https://luajit.org/


12. 延伸阅读

12.1 官方文档

12.2 经典教材

  • 《Programming in Lua》第 4 版(Roberto Ierusalimschy, 2016):第 7-9 章覆盖字符串、表、IO 库,第 22-24 章深入协程、debug、文件操作。
  • 《Lua Quick Start Guide》(Kurt J. Guida, 2018):第 4-6 章覆盖标准库基础。
  • 《Mastering Lua》(Kurt Jung, 2015):第 4-8 章深入标准库高级用法。

12.3 前沿论文

  • Ierusalimschy, R., de Figueiredo, L. H., Celes, W. 2005. The Implementation of Lua 5.0. JUCS. 详解 Lua 5.0 的寄存器式虚拟机与标准库实现。
  • Medeiros, S., Ierusalimschy, R., Bastos, R. 2012. The LuaJIT Project: Performance, Embeddability, and Portability. 讨论 LuaJIT 对标准库的优化。

12.4 开源项目

12.5 社区资源

12.6 相关工具


附录 A:标准库 API 速查

A.1 string 库

API签名返回值
string.len(s)字节数
string.sub(s, i, j)子串
string.find(s, p, init, plain)起止位置或 nil
string.match(s, p, init)匹配内容或捕获
string.gmatch(s, p)迭代器函数
string.gsub(s, p, repl, n)新字符串, 替换次数
string.format(fmt, ...)格式化字符串
string.rep(s, n, sep)重复字符串
string.upper(s)大写
string.lower(s)小写
string.reverse(s)反转
string.byte(s, i, j)字节值
string.char(...)字符串
string.pack(fmt, ...)二进制字符串
string.unpack(fmt, s, pos)值…, 新位置
string.dump(fn, strip)函数字节码

A.2 table 库

API签名返回值
table.insert(t, pos, v)
table.remove(t, pos)被删除元素
table.move(a1, f, e, t, a2)a2 或 a1
table.concat(t, sep, i, j)拼接字符串
table.sort(t, cmp)
table.pack(...){n=…, …}
table.unpack(t, i, j)多返回值

A.3 math 库

API签名返回值
math.abs(x)绝对值
math.floor(x)向下取整
math.ceil(x)向上取整
math.sqrt(x)平方根
math.max(x, ...)最大值
math.min(x, ...)最小值
math.random([m, n])随机数
math.randomseed(x)
math.type(x)”integer” / “float” / nil
math.tointeger(x)整数或 nil

A.4 io 库

API签名返回值
io.open(path, mode)文件句柄或 nil
io.read(...)读取内容
io.write(...)
io.lines([path])迭代器
io.close([file])
io.tmpfile()临时文件句柄
io.popen(cmd, mode)管道句柄
file:read(...)读取内容
file:write(...)
file:lines()迭代器
file:seek(whence, offset)新位置
file:flush()
file:close()

A.5 os 库

API签名返回值
os.time([table])时间戳
os.clock()CPU 时间
os.date(format, time)日期字符串或表
os.difftime(t2, t1)秒差
os.execute(cmd)true/nil, reason, code
os.exit([code, close])不返回
os.getenv(name)环境变量或 nil
os.remove(path)true/nil, err
os.rename(old, new)true/nil, err
os.tmpname()临时文件名
os.setlocale(locale, category)locale 或 nil

A.6 coroutine 库

API签名返回值
coroutine.create(fn)协程
coroutine.resume(co, ...)true/nil, …
coroutine.yield(...)resume 参数
coroutine.status(co)”suspended” / “running” / “normal” / “dead”
coroutine.wrap(fn)函数
coroutine.isyieldable()布尔
coroutine.running()当前协程, 是否主协程

A.7 utf8 库(Lua 5.3+)

API签名返回值
utf8.len(s, i, j)字符数或 nil
utf8.char(...)字符串
utf8.codepoint(s, i, j)码点…
utf8.offset(s, n, i)字节位置
utf8.codes(s)迭代器函数

A.8 debug 库

API签名返回值
debug.traceback([msg, level])调用栈字符串
debug.getinfo(f, what)信息表
debug.getlocal(f, local)名, 值
debug.setlocal(f, local, value)名或 nil
debug.getupvalue(f, up)名, 值
debug.setupvalue(f, up, value)名或 nil
debug.sethook([hook, mask, count])
debug.gethook()hook, mask, count
debug.getregistry()注册表
debug.getmetatable(v)元表或 nil
debug.setmetatable(v, mt)

附录 B:版本兼容性矩阵

B.1 主要 API 的版本支持

APILua 5.1Lua 5.2Lua 5.3Lua 5.4LuaJIT
string.gmatch支持支持支持支持支持
string.pack/unpack不支持不支持支持支持不支持
table.pack不支持支持支持支持支持
table.unpack不支持支持支持支持支持
table.move不支持不支持支持支持支持
math.type不支持不支持支持支持不支持
math.tointeger不支持不支持支持支持不支持
math.maxinteger不支持不支持支持支持支持
utf8不支持不支持支持支持不支持
os.execute 返回状态码true/nil,reason,codetrue/nil,reason,codetrue/nil,reason,code状态码
io.read("L")不支持支持支持支持不支持
coroutine.isyieldable不支持支持支持支持支持

B.2 跨版本兼容性建议

-- 兼容性检测与适配
local LUA_VERSION = _VERSION
local IS_LUAJIT = jit ~= nil

local compat = {
    has_string_pack = LUA_VERSION >= "Lua 5.3",
    has_table_pack = LUA_VERSION >= "Lua 5.2",
    has_table_move = LUA_VERSION >= "Lua 5.3",
    has_math_type = LUA_VERSION >= "Lua 5.3",
    has_utf8 = LUA_VERSION >= "Lua 5.3",
    has_coroutine_isyieldable = LUA_VERSION >= "Lua 5.3",
    is_luajit = IS_LUAJIT,
}

-- 兼容性 polyfill
if not compat.has_table_pack then
    table.pack = function(...)
        return {n = select("#", ...), ...}
    end
end

if not compat.has_table_unpack then
    -- Lua 5.1 的 unpack 是全局函数
    table.unpack = unpack
end

if not compat.has_table_move then
    table.move = function(a1, f, e, t, a2)
        a2 = a2 or a1
        if e >= f then
            local delta = t - f
            if t > f then  -- 正向移动
                for i = f, e do a2[delta + i] = a1[i] end
            else  -- 反向移动(处理重叠)
                for i = e, f, -1 do a2[delta + i] = a1[i] end
            end
        end
        return a2
    end
end

-- 使用兼容层
local function safe_pack(...) return table.pack(...) end
local function safe_unpack(t, i, j)
    return table.unpack(t, i or 1, j or t.n or #t)
end

附录 C:性能特性速查

C.1 常用操作的时间复杂度

操作复杂度备注
#t(数组部分)O(1)O(1)仅数组部分
t[k] = vO(1)O(1)哈希部分常数较大
t[k](读取)O(1)O(1)同上
string.sub(s, i, j)O(ji)O(j-i)创建新字符串
string.find(plain)O(n+m)O(n+m)n=串长, m=模式长
string.find(pattern)O(nm)O(n \cdot m)最坏情况
string.formatO(n)O(n)n=结果长度
string.rep(s, n)$O(n \cdots
table.insert(t, v)O(1)O(1) 摊销末尾插入
table.insert(t, pos, v)O(n)O(n)中间插入需移动
table.remove(t)O(1)O(1)末尾删除
table.remove(t, pos)O(n)O(n)中间删除需移动
table.concatO(L)O(L)L=总长度
table.sortO(nlogn)O(n \log n)introsort
pairs/ipairsO(n)O(n)全表遍历
math.random()O(1)O(1)
io.read("*a")O(n)O(n)n=文件大小
coroutine.resumeO(1)O(1)

C.2 内存分配特性

操作是否分配分配大小
s = s .. "x"新字符串长度
table.insert(t, v)可能表扩容时
string.sub(s, 1, 1)1 字节 + 头部
string.format(...)结果字符串
coroutine.create(fn)协程栈(默认 200 字节)
local t = {}空表(约 56 字节)

附录 D:常见错误信息速查

错误原因解决方案
attempt to index a nil value访问 nil 的字段检查变量是否初始化
attempt to call a nil value调用 nil 函数检查函数名拼写
bad argument #X to 'fn'参数类型错误查看函数文档
invalid escape sequence字符串转义错误使用 % 而非 \
attempt to perform arithmetic on a nil value对 nil 算术运算检查变量赋值
stack overflow递归过深改用迭代或尾递归
attempt to yield from outside a coroutine在协程外 yield检查 yield 调用位置
attempt to concatenate a nil value字符串拼接 nil检查变量是否为 nil
bad argument #X to 'unpack'table.unpack 参数错误确保表存在
invalid format stringstring.format 格式错误检查 % 格式符

更新日志

  • 2026-06-13: 初版内容,覆盖六大标准库基础用法。
  • 2026-07-21: 金标准升级,新增形式化定义、理论推导、对比分析、案例研究、习题、参考文献、延伸阅读、附录等章节,全面达到海外大学教学标准。补充 coroutine 与 utf8 库内容。
返回入门指南