Chapter 12: Data Files and Persistence
写文件比读文件要容易。写文件我们可以获得完全的控制,而读文件我们无法知道会有什么意外发生。
12.1 Data Files
如果数据文件是预定义格式,如CSV,XML,我们选择很少。但是,如果我们想要创建我们的自定义文件,我们可以用Lua constructors 作为文件格式。这种格式将每条数据记录表示成Lua 构造器。
作为这种数据文件的替代:
Donald E. Knuth,Literate Programming,CSLI,1992
Jon Bentley,More Programming Pearls,Addison-Wesley,1990
我们写:
-- 文件data 的内容
Entry{"Donald E. Knuth",
"Literate Programming",
"CSLI",
1992}
Entry{"Jon Bentley",
"More Programming Pearls",
"Addison-Wesley",
1990}
local count = 0
function Entry (_) count = count + 1 end
dofile("data")
print("number of entries: " .. count)
这段代码计算有多少条Entry,Entry{code} 等同于Entry({code}) ,是一个函数调用,table 将为它的参数。所以,数据变成了lua 程序的一部分。
自描述数据格式
-- 文件data
Entry{
author = "Donald E. Knuth",
title = "Literate Programming",
publisher = "CSLI",
year = 1992
}
Entry{
author = "Jon Bentley",
title = "More Programming Pearls",
year = 1990,
publisher = "Addison-Wesley",
}
--
local authors = {} -- a set to collect authors
function Entry (b) authors[b.author] = true end
dofile("data")
for name in pairs(authors) do print(name) end
这段代码将数据文件中的全部author 存进一个表。当某些条目没有author 字段时,我们可以对代码稍作改动:
function Entry (b)
if b.author then authors[b.author] = true end
end
Lua 不只运行快,而且编译快。例如,上面的列出2 M数据所有的authors 不超过一秒。 这不是运气好。数据描述已经成为Lua 中的一种重要应用。
12.2 Serialization
有时我们需要序列化一些数据,就是将数据转换成字节流或字符流,这样我们得以将它存入文件或是通过网络发送出去。
function serialize (o)
if type(o) == "number" then
io.write(o)
elseif type(o) == "string" then
io.write(string.format("%q", o))
else <other cases>
end
end
这一句string.format("%q", a)) 是出于安全的原因,它将任意串包含进两个类似“[[ ]]”的符号里面。避免io.write("[[", o, "]]") 展开成varname = [[ ]]..os.execute('rm *')..[[ ]] 这种危险的形式。
Lua 5.1 提供另一种方法“[=[...]=]” 来处理长串,但是这是一种新符号,主要意图是为手写代码准备的。
用”[==[]==]”的形式包围任意串的函数
function quote (s)
-- find maximum length of sequences of equal signs
local n = -1
for w in string.gmatch(s, "]=*") do
n = math.max(n, #w - 1)
end
-- produce a string with 'n' plus one equal signs
local eq = string.rep("=", n + 1)
-- build quoted string
return string.format(" [%s[/n%s]%s] ", eq, s, eq)
end
s = [===[
[=["aa"dfd[[]]]----]=]
[==[dsaffdsewfdsjk]==]
]===]
ss = quote(s)
print(ss)
Saving tables without cycles
我们的下一个任务(更因难了)是保存表。椐据不同表的限制,有许多不同的方法。并没有单个算法可以应付所有情况。
如果表的数字索引或串索引不是Lua 有效的标识符,我们就有麻烦了。
function serialize (o)
if type(o) == "number" then
io.write(o)
elseif type(o) == "string" then
io.write(string.format("%q", o))
elseif type(o) == "table" then
io.write("{/n")
for k,v in pairs(o) do
io.write(" ", k, " = ")
serialize(v)
io.write(",/n")
end
io.write("}/n")
else
error("cannot serialize a " .. type(o))
end
end
serialize{a=12, b='Lua', key='another "one"'}
Saving tables with cycles
function basicSerialize (o)
if type(o) == "number" then
return tostring(o)
else -- assume it is a string
return string.format("%q", o)
end
end
function save (name, value, saved)
saved = saved or {} -- initial value
io.write(name, " = ")
if type(value) == "number" or type(value) == "string" then
io.write(basicSerialize(value), "/n")
elseif type(value) == "table" then
if saved[value] then -- value already saved?
io.write(saved[value], "/n") -- use its previous name
else
saved[value] = name -- save name for next time
io.write("{}/n") -- create a new table
for k,v in pairs(value) do -- save its fields
k = basicSerialize(k)
local fname = string.format("%s[%s]", name, k)
save(fname, v, saved)
end
end
else
error("cannot save a " .. type(value))
end
end
a = {x=1, y=2; {3,4,5}}
a[2] = a -- cycle
a.z = a[1] -- shared subtable
save("a",a)
上面代码的输出结果是:
---------------------
a = {}
a[1] = {}
a[1][1] = 3
a[1][2] = 4
a[1][3] = 5
a[2] = a
a["y"] = 2
a["x"] = 1
a["z"] = a[1]
----------------------
a = {{"one", "two"}, 3}
b = {k = a[1]}
save("a", a)
save("b", b)
输出结果为:
---------------------
a = {}
a[1] = {}
a[1][1] = "one"
a[1][2] = "two"
a[2] = 3
b = {}
b["k"] = {}
b["k"][1] = "one"
b["k"][2] = "two"
---------------------
a = {{"one", "two"}, 3}
b = {k = a[1]}
local t = {}
save("a", a, t)
save("b", b, t)
输出结果为:
-------------------
a = {}
a[1] = {}
a[1][1] = "one"
a[1][2] = "two"
a[2] = 3
b = {}
b["k"] = a[1]
-------------------