Lua 从入门到精通 / 06 - 表 / Tables
表 / Tables
Table 是 Lua 中唯一的数据结构。数组、字典、对象、模块、命名空间——全部用 table 实现。理解 table,就理解了 Lua 的一半。
Table is Lua’s only data structure. Arrays, dicts, objects, modules, namespaces — all implemented with tables. Understand tables, understand half of Lua.
🟢 基础 / Basics — 创建和使用 Table
1. 创建 Table
-- 空表
local t = {}
-- 数组式(序列)/ Array (sequence)
local fruits = {"apple", "banana", "cherry"}
print(fruits[1]) -- "apple" ← 索引从 1 开始!
-- 字典式 / Dictionary
local person = {
name = "Alice",
age = 30,
city = "Beijing",
}
print(person.name) -- "Alice"
print(person["age"]) -- 30(点号是中括号的语法糖)
-- 混合式 / Mixed
local data = {
"first", -- data[1]
"second", -- data[2]
x = 100, -- data["x"]
y = 200, -- data["y"]
[true] = "yes", -- data[true] ← 键可以是任何非 nil 值
[function() end] = "fn", -- 甚至可以是函数!
}
2. 访问与修改 / Access & Modify
local t = {name = "Lua", version = 5.4}
-- 读取
print(t.name) -- "Lua"
print(t["version"]) -- 5.4
print(t.missing) -- nil(不存在的键返回 nil)
-- 赋值 / 修改
t.version = 5.4
t["new key"] = "hello" -- 键可以有空格
t[1] = "first"
-- 删除:赋值为 nil
t.name = nil -- 等同于删除了 "name" 键
3. 遍历 / Iteration
local t = {name = "Lua", version = 5.4, year = 1993}
-- pairs:遍历所有键值对(顺序不确定)
for k, v in pairs(t) do
print(k, v)
end
-- ipairs:从 1 开始遍历连续的整数键(遇到 nil 停止)
local arr = {10, 20, 30, nil, 50}
for i, v in ipairs(arr) do
print(i, v) -- 只输出 1,2,3(遇到 nil 停止)
end
-- # 运算符:序列的长度
local fruits = {"apple", "banana", "cherry"}
print(#fruits) -- 3
4. Table 作为函数参数 / Table as Function Argument
-- 常见模式:用 table 模拟命名参数
local function createWindow(options)
local width = options.width or 640
local height = options.height or 480
local title = options.title or "Window"
print(string.format("%s: %dx%d", title, width, height))
end
createWindow({
width = 800,
title = "My App",
})
-- 当 table 是唯一参数时,可以省略括号
createWindow{width = 1024, height = 768, title = "Big"}
🟡 进阶 / Intermediate — Table 的实用模式
1. 序列操作 / Sequence Operations
-- table.insert:在指定位置插入
local t = {1, 2, 3}
table.insert(t, 4) -- {1, 2, 3, 4} 尾部插入
table.insert(t, 2, 99) -- {1, 99, 2, 3, 4} 在位置 2 插入
-- table.remove:移除指定位置元素
local t = {1, 2, 3, 4, 5}
table.remove(t, 2) -- 返回 2, t = {1, 3, 4, 5}
table.remove(t) -- 返回 5, t = {1, 3, 4}(移除最后一个)
-- table.sort:排序
local t = {3, 1, 4, 1, 5, 9, 2, 6}
table.sort(t)
-- t = {1, 1, 2, 3, 4, 5, 6, 9}
-- 自定义排序
table.sort(t, function(a, b) return a > b end) -- 降序
-- table.concat:连接为字符串
local t = {"hello", "world", "lua"}
print(table.concat(t, " ")) -- "hello world lua"
print(table.concat(t, ", ")) -- "hello, world, lua"
2. table 作为数组的陷阱 / Array Pitfalls
-- 陷阱 1:不要对非序列使用 #
local t = { [1] = "a", [3] = "c" }
print(#t) -- 1(不是 2!遇到 nil 停止)
-- 陷阱 2:nil 洞会破坏 #
local t = {1, 2, nil, 4, 5}
print(#t) -- 2 或 5(行为未定义!不要这样做)
-- 陷阱 3:pairs 不保证顺序
local t = {a=1, b=2, c=3}
for k, v in pairs(t) do
print(k) -- 顺序不确定!
end
-- 正确做法:需要有序遍历时,手动构建键数组
local t = {a=1, b=2, c=3}
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
table.sort(keys)
for _, k in ipairs(keys) do
print(k, t[k])
end
-- a 1
-- b 2
-- c 3
3. Table 复制 / Table Copy
-- 浅拷贝:只复制第一层
local function shallowCopy(t)
local copy = {}
for k, v in pairs(t) do
copy[k] = v
end
return copy
end
-- 深拷贝:递归复制所有层级
local function deepCopy(t, seen)
if type(t) ~= "table" then return t end
if seen and seen[t] then return seen[t] end -- 处理循环引用
seen = seen or {}
local copy = {}
seen[t] = copy -- 先记录,再递归(防止循环引用)
for k, v in pairs(t) do
copy[deepCopy(k, seen)] = deepCopy(v, seen)
end
return setmetatable(copy, getmetatable(t))
end
-- 测试深拷贝
local original = {a = {x = 1}, b = {y = 2}}
original.self = original -- 循环引用!
local copied = deepCopy(original)
print(copied.a.x) -- 1
print(copied.self == copied) -- true(循环引用被正确处理)
4. Table 合并 / Table Merge
-- 浅合并:将 src 的字段复制到 dst
local function merge(dst, src)
for k, v in pairs(src) do
dst[k] = v
end
return dst
end
-- 使用场景:默认配置 + 用户配置
local defaults = {host="localhost", port=8080, debug=false}
local userConfig = {port=3000, debug=true}
local config = merge({}, defaults)
merge(config, userConfig)
-- config = {host="localhost", port=3000, debug=true}
🔴 高级 / Advanced — Table 的内部实现
1. 双重存储:数组部分 + 哈希部分
Lua table 内部结构 / Internal structure:
local t = {10, 20, 30, x="hello", y="world"}
┌──────────────────────────────────────────┐
│ Table │
├─────────────────┬────────────────────────┤
│ Array Part │ Hash Part │
│ (序列化整数键) │ (其他所有键) │
├─────────────────┼────────────────────────┤
│ [1] = 10 │ "x" ► "hello" │
│ [2] = 20 │ "y" ► "world" │
│ [3] = 30 │ │
├─────────────────┴────────────────────────┤
│ metatable = nil │
│ flags = ... │
└──────────────────────────────────────────┘
何时使用数组部分?何时使用哈希部分?
- 整数键 1~n 的连续序列 → 存入数组部分(O(1) 直接索引)
- 其他键(字符串、不连续整数等)→ 存入哈希部分(O(1) 哈希查找)
2. Rehash 策略
-- 当 table 需要扩容时,Lua 会 rehash
-- Lua uses a power-of-2 growth strategy
-- 插入新键导致 rehash:
local t = {}
for i = 1, 100 do
t[i] = i
end
-- rehash 发生在 1, 2, 4, 8, 16, 32, 64, 128...
-- 每次 rehash 都是 O(n),但分摊后每次插入是 O(1)
-- 性能提示:预分配 table 大小
local t = table.new(100, 0) -- LuaJIT 特有
-- 或
local t = {}
for i = 1, 100 do t[i] = false end -- 预填充
for i = 1, 100 do t[i] = i end -- 实际赋值
3. 弱引用表 / Weak Tables
-- 弱引用表:键或值被弱引用,GC 时可被回收
-- Weak table: keys or values are weakly referenced
-- "k" = 键弱引用,"v" = 值弱引用,"kv" = 都弱引用
local mt = {__mode = "v"} -- 值弱引用
local weak = setmetatable({}, mt)
local obj = {data = "important"}
weak[1] = obj -- weak 持有 obj 的弱引用
print(weak[1]) -- {data = "important"}
obj = nil -- 解除强引用
collectgarbage() -- 触发 GC
print(weak[1]) -- nil!obj 被 GC 回收了
-- 应用场景 1:对象缓存(不阻止 GC)
local cache = setmetatable({}, {__mode = "v"})
local function getObject(id)
if cache[id] then return cache[id] end
local obj = createExpensiveObject(id)
cache[id] = obj
return obj
end
-- 应用场景 2:观察者模式(不阻止 GC)
local listeners = setmetatable({}, {__mode = "k"})
function addListener(obj, callback)
listeners[obj] = callback
end
-- 当 obj 被 GC 后,对应的 listener 自动移除
4. 只读表 / Read-Only Table
-- 利用 __newindex 元方法实现只读表
local function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(_, k, v)
error("Attempt to modify read-only table", 2)
end,
__pairs = function() return pairs(t) end,
__len = function() return #t end,
}
return setmetatable(proxy, mt)
end
local config = readOnly({host="localhost", port=8080})
print(config.host) -- "localhost"
-- config.port = 3000 -- Error: Attempt to modify read-only table
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | 创建 table、[]/. 访问、索引从 1 开始、pairs/ipairs、# 运算符 |
| 🟡 进阶 | table.insert/remove/sort/concat、浅拷贝/深拷贝、合并、命名参数模式 |
| 🔴 高级 | 数组部分+哈希部分、rehash 策略、弱引用表、只读表实现 |