Lua: 用 table 统一实现数组和字典

概述

  • Lua 没有独立的数组或字典类型,而是通过其强大的 table 数据结构来统一实现
  • table 是 Lua 的核心,可以看作是关联数组(associative array),键(key)可以是任意类型(除了 nil),值(value)也可以是任意类型
  • 当键为数字时,table 表现为数组;当键为字符串或其他类型时,table 表现为字典

背景

  • Lua 是一种轻量级脚本语言,最初由巴西里约热内卢天主教大学的研究小组于 1993 年开发
  • 其设计目标是嵌入应用程序,提供灵活的扩展和定制功能
  • table 作为 Lua 中唯一的数据结构,集数组、字典、集合、对象等多种功能于一身,是其灵活性的关键
  • Lua 数组实现
    • Lua 中的数组本质上是使用数字作为键的 table
    • 数组索引通常从 1 开始(虽然也可以使用 0 或负数)

数组与字典的详细实现与实践案例


1 ) 实践案例 1:一维数组

--- 定义一个数组(使用数字键)
local fruits = {"apple", "banana", "orange"}
--- 访问元素(索引从1开始)
print(fruits[1])  -- 输出: apple
print(fruits[2])  -- 输出: banana
--- 遍历数组(使用 ipairs,保证顺序)
for i, fruit in ipairs(fruits) do 
    print(i, fruit)
end 
--- 输出:
--- 1   apple
--- 2   banana
--- 3   orange 

--- 添加元素 
table.insert(fruits, "grape")
print(fruits[4]) -- 输出: grape 
--- 删除元素 
table.remove(fruits, 2) -- 删除索引为2的元素
for i, fruit in ipairs(fruits) do 
    print(i, fruit)
end 
--- 输出:
--- 1   apple
--- 2   orange 
--- 3   grape

特点:索引为数字,通常从1开始;#fruits 或 table.getn(fruits) 可获取数组长度(但对稀疏数组可能不准确);ipairs 遍历时会按数字索引顺序迭代,遇到 nil 值会停止

2 ) 实践案例 2:多维数组

--- 定义一个 3x3 的二维数组(即 table 的 table)
local matrix = {}
for i = 1, 3 do
    matrix[i] = {} -- 每一行是一个新的 table
    for j = 1, 3 do
        matrix[i][j] = i * j -- 填充元素 
    end
end
--- 访问二维数组元素
print(matrix[2][3]) -- 输出: 6

--- 遍历二维数组
for i = 1, 3 do
    for j = 1, 3 do
        print("matrix[" .. i .. "][" .. j .. "] = " .. matrix[i][j])
    end
end

特点:通过嵌套 table 实现;matrix[i][j] 等价于 ((matrix[i])[j])。

Lua 字典实现


Lua 中的字典同样使用 table,但键通常是字符串或其他非数字类型

1 ) 实践案例 1:基础字典操作

--- 定义一个字典(使用字符串键)
local person = {
    name = "Alice", -- 等价于 ["name"] = "Alice"
    age = 30,
    city = "New York"
}
--- 访问元素 
print(person.name)  -- 输出: Alice
print(person["age"]) -- 输出: 30
--- 修改值
person.age = 31
print(person.age) -- 输出: 31
--- 添加键值对 
person.job = "Engineer"
person["salary"] = 50000
--- 删除键值对(通过赋值为 nil)
person.city = nil 
--- 遍历字典(使用 pairs,顺序不定)
for key, value in pairs(person) do
    print(key, value)
end
--- 可能的输出:
--- name    Alice
--- job     Engineer
--- salary  50000
--- age     31

特点:键为字符串(或其它类型),通过 key 访问 value;pairs 遍历时顺序不确定。

2 ) 实践案例 2:混合使用(数组和字典部分)

local mixed_table = {
    "first",      -- 索引1 
    "second",     -- 索引2
    name = "Bob", -- 字典部分
    age = 25      -- 字典部分 
}
--- 访问数组部分 
print(mixed_table[1]) -- 输出: first 
print(mixed_table[2]) -- 输出: second 
--- 访问字典部分
print(mixed_table.name) -- 输出: Bob
print(mixed_table["age"]) -- 输出: 25 
--- 遍历混合 table(需要注意 pairs 和 ipairs 的行为)
print("Using pairs (all key-value pairs):")
for k, v in pairs(mixed_table) do 
    print(k, v)
end 
 
print("Using ipairs (array part only, stops at first nil):")
for i, v in ipairs(mixed_table) do 
    print(i, v)
end 

特点:一个 table 可以同时包含数字索引和字符串索引(或其它类型索引)的元素

数组与字典实现对比表


特性Lua 数组 (Table with Numeric Keys)Lua 字典 (Table with String/Other Keys)
键类型通常是数字 (通常从1开始)通常是字符串或其他类型
访问方式arr[index] 或通过 ipairs 遍历dict.key 或 dict[“key”] 或 pairs 遍历
遍历顺序ipairs 保证顺序 (从1开始连续)pairs 不保证顺序
长度获取#arr (对连续数组有效)无内置函数,需手动计算或使用 pairs 计数
主要用途存储有序、同类型元素存储无序、键值对映射关系
底层实现与字典相同,都是 table与数组相同,都是 table

下面是数组与字典实现逻辑的核心对比:

维度数组实现字典实现
索引规则仅使用连续整数(默认从1开始)支持任意非nil类型(字符串/数字等)
遍历方式优先ipairs、for i=1,#t遍历必须使用pairs遍历
内置方法适配兼容table.insert/sort等数组方法不支持table标准数组方法
典型使用场景有序存储同类型数据键值对映射存储异构关联数据

数组实现扩展案例


1 ) 基础声明与访问:使用大括号直接声明,默认索引从1开始,可手动指定非连续负索引

--- 声明一维数组 
local arr = {"Lua", "Table", "Array"} 
print(arr[1]) --> Lua 
--- 声明负索引数组 
local neg_arr = {} 
neg_arr[-1] = "negative index" 

2 )动态扩容与修改:直接新增索引指向值即可扩容,使用table.insert/table.remove完成指定位置操作[25]

table.insert(arr, 2, "Demo") 
--> arr变为 {"Lua","Demo","Table","Array"} 

3 )多维数组实现:通过表嵌套表来构建,结合嵌套循环初始化与访问

local matrix = {} 
for i=1,3 do 
    matrix[i] = {} 
    for j=1,3 do matrix[i][j] = i*j end 

字典实现实践案例


1 ) 基础声明与访问:支持括号索引与点语法两种写法,点语法仅适用于字符串键且键名不含特殊字符

--- 声明键值对字典 
local user = {name="Alice", age=24, [true]="valid"} 
print(user.name) --> Alice (点语法) 
print(user[true]) --> valid (括号索引) 

2 ) 增删改操作:新增指定索引值完成添加操作,赋值nil即可移除字典元素[10]

user.gender = "female" --新增键值对 
user.age = 25 --修改值 
user.age = nil --删除age键值对 

3 ) 有序遍历字典:将字典键转为数组后排序,基于有序数组遍历获取对应值

local keys = {} 
for k in pairs(user) do table.insert(keys,k) end 
table.sort(keys) --按键排序 
for _,k in ipairs(keys) do print(k, user[k]) end 

混合存储实践


Lua允许在同一个表内同时存储数组与字典逻辑,数组索引与字典键互不干扰[3]

local mix_data = { 
    "first element", "second element", 
    title = "Mixed Table Demo", 
    version = "5.4" 
} 

Lua 数组与字典的核心机制


Lua 仅用 table 一种数据结构实现了数组(顺序表)和字典(关联数组),其设计遵循灵活性优先原则:

  • 数组本质:以连续整数为键的 table。索引默认从 1 开始(符合数学传统),但支持任意起点(如 0 或负数)
  • 字典本质:键值对集合,键可为 string、number 甚至 table,值无类型限制[9]。
  • 混合存储:同一 table 可同时包含数组部分和哈希部分,例如:
     mixed = { "apple", "banana", color = "yellow", price = 2.5 } -- 前两项为数组,后两项为字典  
    

高级案例实践


1 ) 案例 1:游戏地图网格系统(二维数组)

需求:存储 3x3 地图格子类型(0=空地,1=障碍)并快速访问

local map = {}  
for i = 1, 3 do  
    map[i] = {}  -- 每行初始化新数组  
    for j = 1, 3 do  
        map[i][j] = (i == j) and 1 or 0  -- 对角线设为障碍  
    end  
end  
print(map2][2]) -- 输出: 1 (中心障碍)

2 ) 案例 2:角色属性配置(字典 + 元表)

需求:加载角色默认属性,支持自定义覆盖

local defaults = { hp = 100, mp = 50, speed = 10 }  
local hero = setmetatable({ hp = 120 }, { index = defaults })  
print(hero.mp)  -- 输出: 50 (继承默认值)  
hero.speed = 15 -- 自定义覆盖 [9]

3 ) 案例 3:物品合成系统(混合结构)

需求:验证玩家背包材料是否满足合成配方

local recipe = {  
    materials = { "wood", "iron", "iron" },  -- 数组:所需材料列表  
    quantities = { wood = 1, iron = 2 }      -- 字典:材料数量要求  
}

function canCraft(playerInventory)  
    for item, need in pairs(recipe.quantities) do  
        if (playerInventory[item] or 0) < need then  
            return false  
        end  
    end  
    return true  
end  

性能优化技巧


1 ) 数组预分配:避免动态扩容开销,提前初始化大小

local data = {}
for i = 1, 1000 do datai] = 0 end  -- 一次性分配

2 ) 避免哈希冲突:对高频字典使用 string 而非 number 键,减少哈希计算开销

-- 原始方案:数字键 → 冲突风险高 
local userDataByNumber = {}
for i = 1, 10000 do 
    userDataByNumber[i] = { name = "User" .. i } -- 数字键
end
 
-- 高频访问测试(易引发冲突)
for i = 1, 1000000 do
    local key = math.random(1, 10000)  -- 随机键 
    local data = userDataByNumber[key] -- 触发哈希计算
end
 
-- ✅ 优化方案:字符串键 → 减少冲突 
local userDataByString = {}
for i = 1, 10000 do 
    userDataByString[tostring(i)] = { name = "User" .. i } -- 显式转为字符串键
end
 
-- 高频访问测试(哈希计算开销更低)
for i = 1, 1000000 do 
    local key = tostring(math.random(1, 10000)) -- 字符串键
    local data = userDataByString[key]          -- 优先命中缓存哈希值 
end

3 ) 复用 Table:缓存频繁创建的 table,减少 GC 压力

local cache = {}  
function getTempTable()  
    if #cache > 0 then return table.remove(cache) end  
    return {}  
end  

高级技巧与陷阱规避


1 ) 稀疏数组处理:用 ipairs 遍历连续部分,pairs 遍历非连续部分

-- 创建稀疏数组:连续部分 + 非连续键值对 
local sparseArray = {
    "apple",        -- [1] 连续部分 
    "banana",       -- [2] 连续部分
    "cherry",       -- [3] 连续部分
    [5] = "elderberry",  -- 非连续数字键
    [8] = "fig",         -- 非连续数字键 
    color = "red",       -- 非数字键
    [true] = "boolean_key" -- 布尔类型键
}
 
-- 1. 用 ipairs 遍历连续部分 (索引 1 开始的连续整数键)
print("===== 连续部分遍历 (ipairs) =====")
for idx, value in ipairs(sparseArray) do 
    print(string.format("索引 %d: %s", idx, value))
end 
-- 输出:
-- 索引 1: apple
-- 索引 2: banana
-- 索引 3: cherry
 
-- 2. 用 pairs 遍历非连续部分 (跳过连续整数键)
print("\n===== 非连续部分遍历 (pairs) =====")
local maxIndex = #sparseArray  -- 获取连续部分最大索引 (3)
for key, value in pairs(sparseArray) do
    -- 过滤连续部分:跳过 1~maxIndex 的整数键 
    if type(key) ~= "number" 
       or key < 1 
       or key > maxIndex 
       or math.floor(key) ~= key then 
        local keyType = type(key)
        print(string.format("键[%s](%s): %s", 
              tostring(key), keyType, value))
    end
end
-- 输出示例(顺序可能不同):
-- 键[5](number): elderberry 
-- 键[8](number): fig 
-- 键[color](string): red 
-- 键[true](boolean): boolean_key

2 ) 安全空表判断:

function isEmpty(t)  
    return next(t) == nil  -- 高效检测空表 [9]  
end  

3 ) 自定义迭代顺序:通过元表重写 pairs,实现按键名排序输出

-- 自定义迭代器函数:按键名排序遍历表 
local function sorted_pairs(t, order)
    local keys = {}
    for k in pairs(t) do
        table.insert(keys, k)
    end
    
    -- 使用自定义排序规则(默认字母升序)
    table.sort(keys, order)
    
    local i = 0
    return function()
        i = i + 1
        local key = keys[i]
        if key then
            return key, t[key]
        end 
    end
end
 
-- 创建示例表
local data = {
    beta = 2,
    alpha = 1,
    gamma = 3,
    delta = 4
}
 
-- 设置元表重写 __pairs 方法
setmetatable(data, {
    __pairs = function(self)
        return sorted_pairs(self, function(a, b)
            return a < b  -- 自定义排序规则:字母升序
        end)
    end 
})
 
-- 测试:按键名字母顺序输出
print("按键名字母升序输出:")
for k, v in pairs(data) do
    print(k, v)
end
 
-- 可选:使用自定义排序规则(如按键名字母长度)
print("\n按键名长度升序输出:")
for k, v in sorted_pairs(data, function(a, b)
    return #a < #b  -- 按字符串长度排序
end) do
    print(k, v)
end

设计哲学启示


Lua 的 table 设计体现了 “简单即强大” 的理念:

  • 单一结构覆盖数组、字典、类、模块等多重角色
  • 经济性优先:拒绝为小众场景增加语法糖(如内置 OOP),鼓励用基础结构组合实现复杂需求

完整代码示例及性能测试工具可参考:

最佳实践


  1. 根据使用场景选择遍历方式,数组优先使用ipairs保证遍历顺序,字典必须使用pairs
  2. 数组内部尽量保持索引连续以兼容#长度运算符与table系数组方法
  3. 避免在同一个表中大量混合使用数组与字典逻辑,维护时不利于代码可读性与性能优化
  4. 字典排序前务必将键转为独立数组,利用table.sort完成排序后再遍历取值

如果你想深入了解元表对数组与字典的自定义操作,我可以补充相关实践案例与代码示例

要点回顾


  • Lua 的 table 是实现 数组 和 字典 的唯一数据结构
  • 通过数字键访问时,table 表现为数组;通过字符串或其他类型键访问时,table 表现为字典
  • 选择合适的遍历函数:ipairs 用于连续数字索引的数组,pairs 用于所有键值对(包括字典和非连续数组)
  • 数组索引默认从 1 开始,这是 Lua 的约定
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值