<返回目录     Powered by claud/xia兄

第6课: Table详解

Table基础

Table是Lua中唯一的数据结构,可以实现数组、字典、对象等功能。Table在Lua中非常强大,它本质上是一个关联数组,可以使用任何类型(除了nil)作为索引。

Table内部实现原理:

Lua的Table采用混合存储策略,同时包含数组和哈希表两部分。对于连续的整数索引(从1开始),Lua会使用数组部分存储,这样可以提供O(1)的访问速度。对于其他类型的键,Lua使用哈希表存储。这种设计使得Lua的Table在处理数组时非常高效,同时保持了灵活性。

-- 创建空表
local t1 = {}
local t2 = {1, 2, 3}

-- 数组(索引从1开始)
local arr = {"苹果", "香蕉", "橙子"}
print(arr[1])  -- 苹果
print(arr[2])  -- 香蕉
print(#arr)    -- 3 (长度)

-- Table作为字典
local dict = {}
dict["name"] = "张三"
dict[1] = "第一个元素"
dict[true] = "布尔值"
dict[1.5] = "浮点数索引"

-- 遍历所有键值对
for k, v in pairs(dict) do
    print(string.format("[%s] = %s", tostring(k), tostring(v)))
end

数组操作

数组长度计算原理:

Lua的#操作符计算数组长度时,会找到一个边界索引i,使得t[i]不为nil而t[i+1]为nil。这意味着如果数组中有"空洞"(nil值),#操作符可能返回不可预期的结果。对于连续数组,#操作符是O(1)操作,但对于有空洞的数组,可能需要O(n)时间。

-- 创建数组
local numbers = {10, 20, 30, 40, 50}

-- 访问元素
print(numbers[1])  -- 10
print(numbers[5])  -- 50

-- 修改元素
numbers[3] = 35
print(numbers[3])  -- 35

-- 添加元素
numbers[6] = 60
table.insert(numbers, 70)  -- 在末尾添加
table.insert(numbers, 1, 5)  -- 在位置1插入5

-- 删除元素
table.remove(numbers)  -- 删除最后一个
table.remove(numbers, 1)  -- 删除位置1的元素

-- 遍历数组
for i = 1, #numbers do
    print(i, numbers[i])
end

-- 使用ipairs遍历
for index, value in ipairs(numbers) do
    print(index, value)
end

-- 数组长度计算的注意事项
local arr = {1, 2, nil, 4}
print(#arr)  -- 结果可能是2或4,取决于Lua版本和实现

-- 安全的数组遍历方法
local function safeLength(arr)
    local count = 0
    for _ in pairs(arr) do
        count = count + 1
    end
    return count
end

-- 数组切片函数
local function slice(arr, start, finish)
    local result = {}
    start = start or 1
    finish = finish or #arr
    for i = start, finish do
        table.insert(result, arr[i])
    end
    return result
end

local fruits = {"苹果", "香蕉", "橙子", "葡萄", "西瓜"}
local sub = slice(fruits, 2, 4)
print(table.concat(sub, ", "))  -- 香蕉, 橙子, 葡萄

字典(关联数组)

哈希表实现原理:

Lua使用开放寻址法实现哈希表。当发生哈希冲突时,Lua会按照某种策略(通常是线性探测)寻找下一个可用的槽位。Lua会自动调整哈希表的大小,当负载因子超过某个阈值时进行扩容。这种实现方式在大多数情况下都能提供良好的性能,但在极端情况下(大量哈希冲突)性能会下降。

-- 创建字典
local person = {
    name = "张三",
    age = 25,
    city = "北京"
}

-- 访问元素
print(person.name)     -- 张三
print(person["age"])   -- 25

-- 添加/修改元素
person.job = "工程师"
person["salary"] = 10000

-- 删除元素
person.city = nil

-- 遍历字典
for key, value in pairs(person) do
    print(key, value)
end

-- 检查键是否存在
if person["name"] ~= nil then
    print("name键存在")
end

-- 使用不同的键类型
local mixedDict = {
    [1] = "整数键",
    ["string"] = "字符串键",
    [true] = "布尔键",
    [1.5] = "浮点数键",
    [{x=1, y=2}] = "表键"  -- 注意:每次创建新表都是不同的对象
}

-- 深度嵌套的字典
local config = {
    database = {
        host = "localhost",
        port = 3306,
        credentials = {
            username = "admin",
            password = "secret"
        }
    },
    server = {
        port = 8080,
        ssl = true
    }
}

print(config.database.credentials.username)  -- admin

-- 字典合并函数
local function merge(t1, t2)
    local result = {}
    for k, v in pairs(t1) do
        result[k] = v
    end
    for k, v in pairs(t2) do
        result[k] = v
    end
    return result
end

local dict1 = {a = 1, b = 2}
local dict2 = {c = 3, d = 4}
local merged = merge(dict1, dict2)
for k, v in pairs(merged) do
    print(k, v)
end

混合表

-- 同时包含数组和字典部分
local mixed = {
    "第一个元素",  -- [1]
    "第二个元素",  -- [2]
    name = "混合表",
    count = 100
}

print(mixed[1])      -- 第一个元素
print(mixed.name)    -- 混合表
print(#mixed)        -- 2 (只计算数组部分)

-- 遍历所有元素
for k, v in pairs(mixed) do
    print(k, v)
end

Table库函数

Table库函数性能考虑:

table.insert和table.remove在数组末尾操作是O(1)时间复杂度,但在数组中间或开头操作是O(n)时间复杂度,因为需要移动元素。table.sort使用快速排序算法,平均时间复杂度为O(n log n)。table.concat在连接大量字符串时非常高效,因为它预先计算最终字符串大小,避免了多次内存分配。

local t = {3, 1, 4, 1, 5, 9, 2, 6}

-- table.insert(table, [pos,] value)
table.insert(t, 100)      -- 末尾插入
table.insert(t, 1, 0)     -- 位置1插入

-- table.remove(table, [pos])
table.remove(t)           -- 删除最后一个
table.remove(t, 1)        -- 删除位置1

-- table.sort(table, [comp])
table.sort(t)             -- 升序排序
print(table.concat(t, ", "))

table.sort(t, function(a, b) return a > b end)  -- 降序
print(table.concat(t, ", "))

-- table.concat(table, [sep, [i, [j]]])
local words = {"Hello", "Lua", "World"}
print(table.concat(words, " "))  -- Hello Lua World

-- table.unpack(table, [i, [j]])
local a, b, c = table.unpack({10, 20, 30})
print(a, b, c)  -- 10  20  30

-- table.move(table, from, to, target, [dest_table])
local src = {1, 2, 3, 4, 5}
local dest = {0, 0, 0, 0, 0}
table.move(src, 2, 4, 3, dest)
print(table.concat(dest, ", "))  -- 0, 0, 2, 3, 4

-- table.pack(...)
local packed = table.pack("a", "b", "c", nil, "e")
print(packed.n)  -- 5 (元素数量)

-- table.maxn(table) - 已弃用,使用#代替
local arr = {10, 20, 30, [100] = 100}
print(#arr)  -- 3

-- 实用函数:数组去重
local function unique(arr)
    local seen = {}
    local result = {}
    for i, v in ipairs(arr) do
        if not seen[v] then
            seen[v] = true
            table.insert(result, v)
        end
    end
    return result
end

local duplicates = {1, 2, 2, 3, 3, 3, 4}
print(table.concat(unique(duplicates), ", "))  -- 1, 2, 3, 4

-- 实用函数:数组反转
local function reverse(arr)
    local result = {}
    for i = #arr, 1, -1 do
        table.insert(result, arr[i])
    end
    return result
end

local nums = {1, 2, 3, 4, 5}
print(table.concat(reverse(nums), ", "))  -- 5, 4, 3, 2, 1

-- 实用函数:数组分组
local function groupBy(arr, size)
    local result = {}
    for i = 1, #arr, size do
        local group = {}
        for j = i, math.min(i + size - 1, #arr) do
            table.insert(group, arr[j])
        end
        table.insert(result, group)
    end
    return result
end

local data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
local groups = groupBy(data, 3)
for i, group in ipairs(groups) do
    print("Group " .. i .. ": " .. table.concat(group, ", "))
end

多维数组

多维数组实现方式:

Lua中没有真正的多维数组,而是使用"数组的数组"来模拟。每个元素本身是一个数组。这种实现方式灵活但可能带来性能开销,因为每次访问都需要两次索引操作。对于大型矩阵,可以考虑使用一维数组配合索引计算来提高性能。

-- 创建二维数组
local matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
}

-- 访问元素
print(matrix[2][3])  -- 6

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

-- 动态创建二维数组
local rows, cols = 3, 4
local arr = {}
for i = 1, rows do
    arr[i] = {}
    for j = 1, cols do
        arr[i][j] = i * j
    end
end

-- 矩阵转置
local function transpose(matrix)
    local result = {}
    for i = 1, #matrix[1] do
        result[i] = {}
        for j = 1, #matrix do
            result[i][j] = matrix[j][i]
        end
    end
    return result
end

local m = {{1, 2, 3}, {4, 5, 6}}
local tm = transpose(m)
for i = 1, #tm do
    print(table.concat(tm[i], ", "))
end

-- 矩阵乘法
local function matrixMultiply(a, b)
    local rowsA, colsA = #a, #a[1]
    local rowsB, colsB = #b, #b[1]
    
    if colsA ~= rowsB then
        error("矩阵维度不匹配")
    end
    
    local result = {}
    for i = 1, rowsA do
        result[i] = {}
        for j = 1, colsB do
            local sum = 0
            for k = 1, colsA do
                sum = sum + a[i][k] * b[k][j]
            end
            result[i][j] = sum
        end
    end
    return result
end

local mat1 = {{1, 2}, {3, 4}}
local mat2 = {{5, 6}, {7, 8}}
local product = matrixMultiply(mat1, mat2)
for i = 1, #product do
    print(table.concat(product[i], ", "))
end

Table作为集合

集合实现原理:

在Lua中,集合通常通过Table实现,将元素作为键,值设为true。这种实现方式利用了Lua的哈希表,提供了O(1)的平均插入、删除和查找时间复杂度。集合操作(并集、交集、差集)的时间复杂度为O(n),其中n是集合的大小。

-- 使用table实现集合
local set = {}

-- 添加元素
set["apple"] = true
set["banana"] = true
set["orange"] = true

-- 检查元素是否存在
if set["apple"] then
    print("集合中有apple")
end

-- 删除元素
set["banana"] = nil

-- 遍历集合
for element in pairs(set) do
    print(element)
end

-- 集合操作
function union(a, b)
    local result = {}
    for k in pairs(a) do result[k] = true end
    for k in pairs(b) do result[k] = true end
    return result
end

function intersection(a, b)
    local result = {}
    for k in pairs(a) do
        if b[k] then result[k] = true end
    end
    return result
end

function difference(a, b)
    local result = {}
    for k in pairs(a) do
        if not b[k] then result[k] = true end
    end
    return result
end

function isSubset(a, b)
    for k in pairs(a) do
        if not b[k] then return false end
    end
    return true
end

local set1 = {a = true, b = true, c = true}
local set2 = {b = true, c = true, d = true}

print("并集:")
for k in pairs(union(set1, set2)) do print(k) end

print("交集:")
for k in pairs(intersection(set1, set2)) do print(k) end

print("差集 (set1 - set2):")
for k in pairs(difference(set1, set2)) do print(k) end

print("set1是set2的子集吗?", isSubset(set1, set2))

-- 有序集合(使用数组+table)
local OrderedSet = {}
OrderedSet.__index = OrderedSet

function OrderedSet.new()
    return setmetatable({
        items = {},
        lookup = {}
    }, OrderedSet)
end

function OrderedSet:add(item)
    if not self.lookup[item] then
        table.insert(self.items, item)
        self.lookup[item] = #self.items
    end
end

function OrderedSet:remove(item)
    local index = self.lookup[item]
    if index then
        table.remove(self.items, index)
        self.lookup[item] = nil
        -- 更新后续元素的索引
        for i = index, #self.items do
            self.lookup[self.items[i]] = i
        end
    end
end

function OrderedSet:contains(item)
    return self.lookup[item] ~= nil
end

function OrderedSet:iterate()
    local i = 0
    return function()
        i = i + 1
        return self.items[i]
    end
end

local oset = OrderedSet.new()
oset:add("first")
oset:add("second")
oset:add("third")
oset:add("first")  -- 不会重复添加

for item in oset:iterate() do
    print(item)
end

高级Table概念

弱引用表(Weak Tables):

弱引用表允许垃圾回收器回收表中的键或值,即使它们仍然被弱引用表引用。通过设置表的__mode元字段为"k"、"v"或"kv",可以分别使键、值或两者都成为弱引用。这在实现缓存、对象池等场景中非常有用。

-- 弱引用表示例
local weakTable = setmetatable({}, {__mode = "v"})

local key = {}
weakTable[key] = "value"

print(weakTable[key])  -- value

-- 删除对value的强引用
value = nil

-- 强制垃圾回收
collectgarbage()

-- 值已被回收
print(weakTable[key])  -- nil

-- 对象池实现
local ObjectPool = {}
ObjectPool.__index = ObjectPool

function ObjectPool.new()
    return setmetatable({
        pool = setmetatable({}, {__mode = "v"}),
        factory = function() return {} end
    }, ObjectPool)
end

function ObjectPool:acquire()
    local obj = table.remove(self.pool)
    if not obj then
        obj = self.factory()
    end
    return obj
end

function ObjectPool:release(obj)
    table.insert(self.pool, obj)
end

-- 缓存实现
local memoize = function(func)
    local cache = setmetatable({}, {__mode = "k"})
    return function(x)
        local r = cache[x]
        if not r then
            r = func(x)
            cache[x] = r
        end
        return r
    end
end

local slowFib = function(n)
    if n < 2 then return n end
    return slowFib(n - 1) + slowFib(n - 2)
end

local fastFib = memoize(slowFib)
print(fastFib(35))  -- 计算很快,因为使用了缓存

Table序列化

-- 简单的Table序列化
local function serialize(t)
    local result = {}
    local function helper(t, indent)
        indent = indent or ""
        for k, v in pairs(t) do
            local key = type(k) == "string" and k or "[" .. tostring(k) .. "]"
            if type(v) == "table" then
                table.insert(result, indent .. key .. " = {")
                helper(v, indent .. "  ")
                table.insert(result, indent .. "},")
            else
                local value = type(v) == "string" and '"' .. v .. '"' or tostring(v)
                table.insert(result, indent .. key .. " = " .. value .. ",")
            end
        end
    end
    helper(t)
    return table.concat(result, "\n")
end

local data = {
    name = "张三",
    age = 25,
    scores = {90, 85, 95},
    address = {
        city = "北京",
        street = "长安街"
    }
}

print(serialize(data))

-- Table比较(深度比较)
local function deepEqual(t1, t2)
    if t1 == t2 then return true end
    if type(t1) ~= "table" or type(t2) ~= "table" then return false end
    
    for k in pairs(t1) do
        if not deepEqual(t1[k], t2[k]) then return false end
    end
    
    for k in pairs(t2) do
        if t1[k] == nil then return false end
    end
    
    return true
end

local t1 = {a = 1, b = {c = 2}}
local t2 = {a = 1, b = {c = 2}}
local t3 = {a = 1, b = {c = 3}}

print(deepEqual(t1, t2))  -- true
print(deepEqual(t1, t3))  -- false

Table的引用特性

引用语义详解:

Table是引用类型,赋值时传递的是引用而非副本。这意味着多个变量可以指向同一个Table,修改其中一个会影响所有引用。理解这一点对于避免意外的副作用非常重要。在需要独立副本时,必须显式进行浅拷贝或深拷贝。

-- Table是引用类型
local t1 = {1, 2, 3}
local t2 = t1  -- t2引用t1

t2[1] = 100
print(t1[1])  -- 100 (t1也被修改了)

-- 复制table(浅拷贝)
function shallowCopy(original)
    local copy = {}
    for k, v in pairs(original) do
        copy[k] = v
    end
    return copy
end

local t3 = shallowCopy(t1)
t3[1] = 200
print(t1[1])  -- 100 (t1不受影响)

-- 深拷贝
function deepCopy(original)
    local copy
    if type(original) == "table" then
        copy = {}
        for k, v in pairs(original) do
            copy[deepCopy(k)] = deepCopy(v)
        end
    else
        copy = original
    end
    return copy
end

-- 测试深拷贝
local original = {
    a = 1,
    b = {c = 2, d = {e = 3}}
}

local shallow = shallowCopy(original)
shallow.b.c = 100
print(original.b.c)  -- 100 (原对象被修改)

local deep = deepCopy(original)
deep.b.d.e = 200
print(original.b.d.e)  -- 3 (原对象不受影响)

-- 函数参数中的Table
function modifyTable(t)
    t.newKey = "new value"
end

local myTable = {oldKey = "old value"}
modifyTable(myTable)
print(myTable.newKey)  -- new value

-- 避免意外修改的技巧
function safeModify(t)
    local copy = deepCopy(t)
    copy.newKey = "new value"
    return copy
end

local safeTable = safeModify(myTable)
print(myTable.newKey)  -- new value (原表仍被修改,因为myTable本身就是引用)

综合示例:简单的数据库系统

-- 简单的内存数据库系统
local SimpleDB = {}
SimpleDB.__index = SimpleDB

function SimpleDB.new()
    return setmetatable({
        tables = {},
        indexes = {}
    }, SimpleDB)
end

function SimpleDB:createTable(name, schema)
    self.tables[name] = {
        schema = schema,
        data = {},
        autoIncrement = 1
    }
    self.indexes[name] = {}
    for field in pairs(schema) do
        self.indexes[name][field] = {}
    end
end

function SimpleDB:insert(tableName, record)
    local table = self.tables[tableName]
    if not table then
        error("Table '" .. tableName .. "' does not exist")
    end
    
    local id = table.autoIncrement
    record.id = id
    table.data[id] = record
    
    -- 更新索引
    for field in pairs(table.schema) do
        if record[field] then
            local index = self.indexes[tableName][field]
            local key = tostring(record[field])
            if not index[key] then
                index[key] = {}
            end
            table.insert(index[key], id)
        end
    end
    
    table.autoIncrement = table.autoIncrement + 1
    return id
end

function SimpleDB:select(tableName, conditions)
    local table = self.tables[tableName]
    if not table then
        error("Table '" .. tableName .. "' does not exist")
    end
    
    local results = {}
    
    if not conditions or next(conditions) == nil then
        -- 返回所有记录
        for id, record in pairs(table.data) do
            table.insert(results, record)
        end
    else
        -- 使用索引优化查询
        local candidateIds = nil
        
        for field, value in pairs(conditions) do
            local index = self.indexes[tableName][field]
            local key = tostring(value)
            
            if index[key] then
                if not candidateIds then
                    candidateIds = {}
                    for _, id in ipairs(index[key]) do
                        candidateIds[id] = true
                    end
                else
                    -- 取交集
                    local newCandidates = {}
                    for _, id in ipairs(index[key]) do
                        if candidateIds[id] then
                            newCandidates[id] = true
                        end
                    end
                    candidateIds = newCandidates
                end
            else
                -- 没有匹配的记录
                return {}
            end
        end
        
        if candidateIds then
            for id in pairs(candidateIds) do
                local record = table.data[id]
                local match = true
                for field, value in pairs(conditions) do
                    if record[field] ~= value then
                        match = false
                        break
                    end
                end
                if match then
                    table.insert(results, record)
                end
            end
        end
    end
    
    return results
end

function SimpleDB:update(tableName, conditions, updates)
    local records = self:select(tableName, conditions)
    for _, record in ipairs(records) do
        for field, value in pairs(updates) do
            record[field] = value
        end
    end
    return #records
end

function SimpleDB:delete(tableName, conditions)
    local table = self.tables[tableName]
    local records = self:select(tableName, conditions)
    local count = #records
    
    for _, record in ipairs(records) do
        local id = record.id
        table.data[id] = nil
        
        -- 从索引中删除
        for field in pairs(table.schema) do
            local index = self.indexes[tableName][field]
            local key = tostring(record[field])
            if index[key] then
                for i, idxId in ipairs(index[key]) do
                    if idxId == id then
                        table.remove(index[key], i)
                        break
                    end
                end
            end
        end
    end
    
    return count
end

-- 使用示例
local db = SimpleDB.new()

-- 创建表
db:createTable("users", {
    name = "string",
    age = "number",
    email = "string"
})

-- 插入数据
local id1 = db:insert("users", {name = "张三", age = 25, email = "zhangsan@example.com"})
local id2 = db:insert("users", {name = "李四", age = 30, email = "lisi@example.com"})
local id3 = db:insert("users", {name = "王五", age = 25, email = "wangwu@example.com"})

-- 查询所有用户
print("所有用户:")
for _, user in ipairs(db:select("users")) do
    print(string.format("%d: %s, %d岁, %s", user.id, user.name, user.age, user.email))
end

-- 条件查询
print("\n年龄为25岁的用户:")
for _, user in ipairs(db:select("users", {age = 25})) do
    print(user.name)
end

-- 更新数据
db:update("users", {name = "张三"}, {age = 26})

-- 删除数据
db:delete("users", {name = "王五"})

print("\n更新后的用户:")
for _, user in ipairs(db:select("users")) do
    print(string.format("%d: %s, %d岁", user.id, user.name, user.age))
end
重要提示:
练习题:
  1. 创建一个学生信息表,包含姓名、年龄、成绩
  2. 实现函数对数组进行去重
  3. 创建3x3矩阵并计算对角线元素之和
  4. 使用table实现栈(push、pop操作)