21.1 简单I/O模式

简单模式的所有操作都是在两个当前文件之上。I/O库将当前输入文件作为标准输入(stdin),将当前输出文件作为标准输出(stdout)。这样当我们执行io.read,就是在标准输入中读取一行。我们可以使用io.inputio.output函数来改变当前文件。例如io.input(filename)就是打开给定文件(以读模式),并将其设置为当前输入文件。接下来所有的输入都来自于该文,直到再次使用io.inputio.output函数。类似于io.input。一旦产生错误两个函数都会产生错误。如果你想直接控制错误必须使用完全模式中io.read函数。写操作较读操作简单,我们先从写操作入手。下面这个例子里函数io.write获取任意数目的字符串参数,接着将它们写到当前的输出文件。通常数字转换为字符串是按照通常的规则,如果要控制这一转换,可以使用string库中的format函数:

> io.write("sin (3) = ", math.sin(3), "\n")

  --> sin (3) = 0.1411200080598672

> io.write(string.format("sin (3) = %.4f\n", math.sin(3)))

  --> sin (3) = 0.1411

在编写代码时应当避免像io.write(a..b..c);这样的书写,这同io.write(a,b,c)的效果是一样的。但是后者因为避免了串联操作,而消耗较少的资源。原则上当你进行粗略(quick and dirty)编程,或者进行排错时常使用print函数。当需要完全控制输出时使用write

> print("hello", "Lua"); print("Hi")

  --> hello   Lua

  --> Hi

 

> io.write("hello", "Lua"); io.write("Hi", "\n")

  --> helloLuaHi

Write函数与print函数不同在于,write不附加任何额外的字符到输出中去,例如制表符,换行符等等。还有write函数是使用当前输出文件,而print始终使用标准输出。另外print函数会自动调用参数的tostring方法,所以可以显示出表(tables)函数(functions)和nil

read函数从当前输入文件读取串,由它的参数控制读取的内容:

"*all"

读取整个文件

"*line"

读取下一行

"*number"

从串中转换出一个数值

num

读取num个字符到串

io.read("*all")函数从当前位置读取整个输入文件。如果当前位置在文件末尾,或者文件为空,函数将返回空串。由于Lua对长串类型值的有效管理,在Lua中使用过滤器的简单方法就是读取整个文件到串中去,处理完之后(例如使用函数gsub),接着写到输出中去:

t = io.read("*all")         -- read the whole file

t = string.gsub(t, ...)     -- do the job

io.write(t)                 -- write the file

以下代码是一个完整的处理字符串的例子。文件的内容要使用MIME(多用途的网际邮件扩充协议)中的quoted-printable码进行编码。以这种形式编码,非ASCII字符将被编码为“=XX”,其中XX是该字符值的十六进制表示,为表示一致性“=”字符同样要求被改写。在gsub函数中的“模式”参数的作用就是得到所有值在128255之间的字符,给它们加上等号标志。

t = io.read("*all")

t = string.gsub(t, "([\128-\255=])", function (c)

    return string.format("=%02X", string.byte(c))

end)

io.write(t)

该程序在奔腾333MHz环境下转换200k字符需要0.2秒。

io.read("*line")函数返回当前输入文件的下一行(不包含最后的换行符)。当到达文件末尾,返回值为nil(表示没有下一行可返回)。该读取方式是read函数的默认方式,所以可以简写为io.read()。通常使用这种方式读取文件是由于对文件的操作是自然逐行进行的,否则更倾向于使用*all一次读取整个文件,或者稍后见到的逐块的读取文件。下面的程序演示了应如何使用该模式读取文件。此程序复制当前输入文件到输出文件,并记录行数。

local count = 1

while true do

    local line = io.read()

    if line == nil then break end

    io.write(string.format("%6d  ", count), line, "\n")

    count = count + 1

end

然而为了在整个文件中逐行迭代。我们最好使用io.lines迭代器。例如对文件的行进行排序的程序如下:

local lines = {}

-- read the lines in table 'lines'

for line in io.lines() do

    table.insert(lines, line)

end

-- sort

table.sort(lines)

-- write all the lines

for i, l in ipairs(lines) do io.write(l, "\n") end

在奔腾333MHz上该程序处理处理4.5MB大小,32K行的文件耗时1.8秒,比使用高度优化的C语言系统排序程序快0.6秒。io.read("*number")函数从当前输入文件中读取出一个数值。只有在该参数下read函数才返回数值,而不是字符串。当需要从一个文件中读取大量数字时,数字间的字符串为空白可以显著的提高执行性能。*number选项会跳过两个可被识别数字之间的任意空格。这些可识别的字符串可以是-3+5.21000,和 -3.4e-23。如果在当前位置找不到一个数字(由于格式不对,或者是到了文件的结尾),则返回nil 可以对每个参数设置选项,函数将返回各自的结果。假如有一个文件每行包含三个数字:

6.0        -3.23         15e12

4.3        234           1000001

...

现在要打印出每行最大的一个数,就可以使用一次read函数调用来读取出每行的全部三个数字:

while true do

    local n1, n2, n3 = io.read("*number", "*number", "*number")

    if not n1 then break end

    print(math.max(n1, n2, n3))

end

在任何情况下,都应该考虑选择使用io.read函数的 " *.all " 选项读取整个文件,然后使用gfind函数来分解:

local pat = "(%S+)%s+(%S+)%s+(%S+)%s+"

for n1, n2, n3 in string.gfind(io.read("*all"), pat) do

    print(math.max(n1, n2, n3))

end

除了基本读取方式外,还可以将数值n作为read函数的参数。在这样的情况下read函数将尝试从输入文件中读取n个字符。如果无法读取到任何字符(已经到了文件末尾),函数返回nil。否则返回一个最多包含n个字符的串。以下是关于该read函数参数的一个进行高效文件复制的例子程序(当然是指在Lua中)

local size = 2^13        -- good buffer size (8K)

while true do

    local block = io.read(size)

    if not block then break end

    io.write(block)

end

特别的,io.read(0)函数的可以用来测试是否到达了文件末尾。如果不是返回一个空串,如果已是文件末尾返回nil


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛