快速掌握Lua 5.3 —— 数据操作

Q:如何利用Lua构造器操作数据?

A:将每条数据作为Lua中的一个”table”构造,将数据中的每一个域作为”table”中的一个”value”。比如下面这些数据,

Donald E. Knuth,Literate Programming,CSLI,1992
Jon Bentley,More Programming Pearls,Addison-Wesley,1990

在Lua中使用”table”构造可以表示为,

-- "data.lua"文件中内容。
Entry{"Donald E. Knuth",
      "Literate Programming",
      "CSLI",
      1992}
Entry{"Jon Bentley",
      "More Programming Pearls",
      "Addison-Wesley",
      1990}

-- 因为"Entry{}"与"Entry({})"相同,所以之后在Lua中可以使用回调函数的方式使用这些数据,函数的参数就是数据本身。
-- 计算数据中有几条记录。
local count = 0
function Entry (b) count = count + 1 end
dofile("data.lua")    -- 引入文件内容并执行。
print("number of entries: " .. count)    --> number of entries: 2

-- 存储文件中所有作者名(Entry的第一个域),并打印他们。
local authors = {}      -- a set to collect authors
function Entry (b) authors[b[1]] = true end
dofile("data.lua")    -- 引入文件内容并执行。
for name in pairs(authors) do print(name) end

但是上面的最后一个例子存在潜在的隐患。如果有的数据没有”author”域,那么b[1]取到的就不一定是什么值了。

-- 所以如果对数据大小不太关心的话,可以使用"self-describing data format"的方式存储数据,每个变量值的含义都有一个名称来描述。
Entry{
  author = "Donald E. Knuth",
  title = "Literate Programming",
  publisher = "CSLI",
  year = 1992
}
Entry{
  author = "Jon Bentley",
  title = "More Programming Pearls",
  publisher = "Addison-Wesley",
  year = 1990
}

-- 那么上面的最后一个例子可以更改为:
local authors = {}    -- a set to collect authors
function Entry (b)
    --[[ 有了"self-describing data format",这里就可以判断记录中是否有该域了。
         同时"author"写在数据中的哪个域都没关系(不一定必须写在第一个域了)。]]
    if b.author then authors[b.author] = true end
end
dofile("data.lua")    -- 引入文件内容并执行。
for name in pairs(authors) do print(name) end

Q:什么是数据的序列化?

A:我们经常需要序列化一些数据,为了将数据转换为字节流或者字符流,这样我们就
可以保存到文件或者通过网络发送出去。

-- 对数字的序列化。
if type(o) == "number" then
    io.write(o)
end

-- 对字符串的序列化。
if type(o) == "number" then
    io.write(string.format("%q", o))    -- "%q"可以安全的处理字符串。详见"附加1"。
end

Q:如何将数据序列化为”expressions”?

A:

--[[ o: 待序列化的数据。
     pre_type: 待序列化的数据被包含在哪种类型的数据中。
     indent: 缩进的个数。]]
function serialize (o, pre_type, indent)
    indent = indent or 0    -- 缩进的个数。默认是0,即不缩进。
    if type(o) == "number" then    -- 对数字的序列化。
        io.write(string.format("%d", o))
        -- 如果数据是在"table"中,那么不能换行,因为之后有数据要打印。
        if("table" ~= pre_type) then
            io.write("\n")
        end
    elseif type(o) == "string" then    -- 对字符串的序列化。
        io.write(string.format("%q", o))
        -- 如果数据是在"table"中,那么不能换行,因为之后有数据要打印。
        if("table" ~= pre_type) then
            io.write("\n")
        end
    elseif type(o) == "table" then    -- 对"table"的序列化。
        io.write("{\n")
        for k,v in pairs(o) do
            for i = indent, 0, -1 do
                io.write("\t")
            end
            io.write("[")
            serialize(k, "table", indent + 1)    -- 对"key"继续进行序列化。
            io.write("] = ")
            serialize(v, "table", indent + 1)    -- 对"value"继续进行序列化。
            io.write(",\n")
        end
        -- "}"需要少打印一个缩进,否则与其中的数据一样的缩进了。
        for i = indent - 1, 0, -1 do
            io.write("\t")
        end
        io.write("}")
    else
        error("cannot serialize a " .. type(o))
    end
end

-- data
number = 5
string = "abcdefg"
table = {
    5, 
    'one', 
    'another "one"',
    [10] = 9, 
    first_str_index = 23,  
    ["second_str_index"] = 67,
    {
        1,
        2,
        {
            x = 12,
            y = 56
        },
        3
    }
}

-- test
serialize(number)
serialize(string)
serialize(table)
io.write("\n")

-- result
5
"abcdefg"
{
    [1] = 5,
    [2] = "one",
    [3] = "another \"one\"",
    [4] = {
        [1] = 1,
        [2] = 2,
        [3] = {
            ["x"] = 12,
            ["y"] = 56,
        },
        [4] = 3,
    },
    ["first_str_index"] = 23,
    [10] = 9,
    ["second_str_index"] = 67,
}

接收到被序列化为”expressions”数据的程序可以使用变量存储数据。

Q:如何将数据序列化为”statements”?

A:

--[[ 限制被序列化的数据只能是"number"或者"string"。
     (对"table"的序列化,主函数中有特殊处理。)]]
function basicSerialize (o)
    if type(o) == "number" then
        return tostring(o)
    else   -- assume it is a string
        return string.format("%q", o)
    end
end

--[[ name: 被序列化的数据的变量名。
     value: 被序列化的数据的变量值。
     saved: 存储已经被序列化的数据的表。]]
function save (name, value, saved)
    saved = saved or {}    -- 存储已经被序列化的数据。
    io.write(name, " = ")    -- 打印变量名。
    -- 如果数据是"number"或者"string",则直接打印变量值。
    if type(value) == "number" or type(value) == "string" then
        io.write(basicSerialize(value), "\n")
    -- 如果数据是"table"。
    elseif type(value) == "table" then
        if saved[value] then
            -- 如果该数据被序列化过,则不重复序列化,直接打印该数据的名字。
            io.write(saved[value], "\n")
        else    -- 如果该数据未被序列化过。
            saved[value] = name    -- 在saved中存储该数据值与名字的映射。
            io.write("{}\n")     -- create a new table
            -- 依次序列化"table"中的每一个域。
            for k,v in pairs(value) do
                local fieldname = string.format("%s[%s]", name,
                basicSerialize(k))
                save(fieldname, v, saved)
            end
        end
    else
        error("cannot save a " .. type(value))
    end
end

-- data
number = 5
string = "abcdefg"
table_2 = {
    x = 12,
    y = 56
}
table_1 = {
    1,
    2,
    table_2, 
    3
}
table = {
    5,
    'one',
    'another "one"',
    [10] = 9,
    first_str_index = 23,
    ["second_str_index"] = 67,
    table_1, 
    table_2    -- "table_2"在上面的"table_1"中已经被序列化过,不会被重复序列化。
}

-- test
save("var_n", number)
save("var_s", string)
save("var_t", table)

-- result
var_n = 5
var_s = "abcdefg"
-- ↓ 这一部分将"table_1"序列化。
var_t = {}
var_t[1] = 5
var_t[2] = "one"
var_t[3] = "another \"one\""
var_t[4] = {}
var_t[4][1] = 1
var_t[4][2] = 2
-- ↓ 这一部分将"table_2"序列化。
var_t[4][3] = {}
var_t[4][3]["x"] = 12
var_t[4][3]["y"] = 56
-- ↑ 这一部分将"table_2"序列化。
var_t[4][4] = 3
var_t[5] = var_t[4][3]    <-- "table_2"已经被序列化过,不会被重复序列化,只是打印被序列化时的名字。
var_t["first_str_index"] = 23
var_t[10] = 9
var_t["second_str_index"] = 67
-- ↑ 这一部分将"table_1"序列化。

接收到被序列化为”statements”数据的程序可以将数据直接存储在Lua脚本中,之后直接执行脚本使用。

附加:

1、对字符串的序列化最开始的想法可能是,

if type(o) == "string" then
    io.write("'", o, "'")
end

但是这种实现方式对于一些特殊的字符(比如单引号,换行符等)无效。所以实现方式可能演变为,

-- WARNING: bad code ahead!!
if type(o) == "string" then
    io.write("[[", o, "]]")
end

千万不要如此实现!!!
双引号是针对手写的字符串的而不是针对自动产生的字符串。如果有人恶意的引导你的程序去使用" ]]..os.execute('rm *')..[[ "这样的方式序列化,你最终的”chunk”将是这个样子,

varname = [[ ]]..os.execute('rm *')..[[ ]]

执行这句代码的后果可想而知。
为了以安全的方式引用任意的字符串,”string”标准库提供了格式化函数专门提供"%q"选项。它可以使用双引号表示字符串并且可以正确的处理包含引号和换行等特殊字符的字符串。
2、对于将数据序列化为”statements”的例子,你可以控制共享的数据是否需要被重复序列化。
共享的数据会被重复序列化,

-- data
a = {{"one", "two"}, 3}
b = {k = a[1]}

-- test
-- 未指定"save()"的第三个参数,所以每次调用"save()",其中的"saved"都是一个新的"table"。
save('a', a)
save('b', b)

-- result
a = {}
-- ↓ 这一部分将"a[1]"序列化。
a[1] = {}
a[1][1] = "one"
a[1][2] = "two"
-- ↑ 这一部分将"a[1]"序列化。
a[2] = 3
b = {}
-- ↓ 这一部分将"a[1]"重复序列化,只是换了个名字。
b["k"] = {}
b["k"][1] = "one"
b["k"][2] = "two"
-- ↑ 这一部分将"a[1]"重复序列化,只是换了个名字。

共享的数据不会被重复序列化,

-- test
local t = {}
-- 制定了"save()"的第三个参数,所以每次调用"save()",其中的"saved"都是相同的"table"。
save('a', a, t)
save('b', b, t)

-- result
a = {}
-- ↓ 这一部分将"a[1]"序列化。
a[1] = {}
a[1][1] = "one"
a[1][2] = "two"
-- ↑ 这一部分将"a[1]"序列化。
a[2] = 3
b = {}
b["k"] = a[1]    <-- "a[1]"未被重复序列化。

你可能感兴趣的:(lua)