第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于后续版本,但有一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还将帮助支持 Lua 项目


12 – 数据文件和持久性

在处理数据文件时,通常写数据比读回数据容易得多。当我们写文件时,我们可以完全控制正在发生的事情。另一方面,当我们读文件时,我们不知道会发生什么。除了正确文件可能包含的所有类型的数据之外,健壮的程序还应该优雅地处理错误文件。因此,编写健壮的输入例程总是很困难。

正如我们在 第 10.1 节 的示例中所看到的,表构造函数为文件格式提供了一个有趣的替代方案。在写数据时只需稍加工作,读取就会变得非常简单。这种技术是将我们的数据文件写成 Lua 代码,在运行时将数据构建到程序中。使用表构造函数,这些块看起来非常像一个普通数据文件。

和往常一样,让我们看一个示例来阐明问题。如果我们的数据文件采用预定义的格式,例如 CSV(逗号分隔值),我们几乎没有选择。(在 第 20 章 中,我们将看到如何在 Lua 中读取 CSV。)但是,如果我们要创建文件以供以后使用,我们可以使用 Lua 构造函数作为我们的格式,而不是 CSV。在这种格式中,我们将每条数据记录表示为一个 Lua 构造函数。我们不写类似于

    Donald E. Knuth,Literate Programming,CSLI,1992
    Jon Bentley,More Programming Pearls,Addison-Wesley,1990
这样的内容,而是在数据文件中写
    Entry{"Donald E. Knuth",
          "Literate Programming",
          "CSLI",
          1992}
    
    Entry{"Jon Bentley",
          "More Programming Pearls",
          "Addison-Wesley",
          1990}
请记住,Entry{...}Entry({...}) 相同,即调用函数 Entry,其参数是一个表。因此,此先前的这部分数据是一个 Lua 程序。要读取此文件,我们只需要运行它,并为 Entry 提供一个明智的定义。例如,以下程序统计数据文件中的条目数
    local count = 0
    function Entry (b) count = count + 1 end
    dofile("data")
    print("number of entries: " .. count)
下一个程序在文件中收集所有作者的姓名,然后打印出来。(作者姓名是每个条目中的第一个字段;因此,如果 b 是一个条目值,b[1] 是作者。)
    local authors = {}      -- a set to collect authors
    function Entry (b) authors[b[1]] = true end
    dofile("data")
    for name in pairs(authors) do print(name) end
请注意这些程序片段中的事件驱动方法:Entry 函数充当回调函数,在 dofile 期间针对数据文件中的每个条目调用该函数。

当文件大小不是一个大问题时,我们可以使用名称-值对进行表示

    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
    }
(如果此格式让您想起了 BibTeX,那不是巧合。BibTeX 是 Lua 中构造函数语法的灵感来源之一。)这种格式是我们所说的自描述数据格式,因为每条数据都附有对其含义的简短描述。自描述数据比 CSV 或其他紧凑表示法更具可读性(至少对人类而言);必要时,它们很容易手动编辑;并且它们允许我们进行小的修改,而无需更改数据文件。例如,如果我们添加一个新字段,我们只需要对读取程序进行一个小小的更改,以便在字段不存在时提供一个默认值。

使用 name-value 格式,我们收集作者的程序变为

    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
现在字段的顺序无关紧要。即使某些条目没有作者,我们只需要更改 Entry
    function Entry (b)
      if b.author then authors[b.author] = true end
    end

Lua 不仅运行速度快,而且编译速度也快。例如,用于列出作者的上述程序在 2 MB 的数据中运行时间不到一秒。同样,这不是偶然的。数据描述一直是 Lua 自创建以来主要应用之一,我们非常注意使其编译器对大块数据运行得很快。