I/O库为文件操作提供了两种不同的模型,简单模型(simple model)和完整模型(complete
model)。简单模型假设有一个当前输入文件和一个当前输出文件,它的I/O操作均作用于
这些文件。完整模型则使用显示的文件句柄。它采用了面向对象的风格,并将所有的操作
定义为文件句柄上的方法。
21.1 简单I/O模型
简单模型的所有操作都作用于两个当前文件。I/O库将当前输入文件初始化为进程标准输入,
将当前输出文件初始化为进程标准输出。在执行io.read()时,就出从标出输入中读取一行。
函数io.input和io.output可以改变这两个当前文件。io.input(filename)调用会以只读模式打开
指定文件,并将其设为当前输入文件;在输出方面,io.output也可以完成类似的工作;如果
出现错误,这两个函数都会引发错误。如果想直接处理这些错误,则必须使用完整模型中的
io.open.
函数io.write可以接受任意数量的字符串参数,并将它们写入当前输出文件。它也可以接受数
字参数,数字参数会根据常规的转换规则转换为字符串。如果想要控制这种转换,则应该使
用函数string.format.
write 与 print的几点不同:
首先,write在输出时不会添加像制表符或回车这样的额外字符;
其次,write使用当前输出文件,而print总是使用标准输出;
最后,print会自动调用其参数的tostring()方法,因此它还能显示table、函数和nil。
函数io.read从当前输入文件中读取字符串,它的参数决定了要读取的数据:
"*all" 读取整个文件
"*line" 读取下一行
"*number" 读取一个数字
<num> 读取一个不超过<num>个字符的
字符串调用io.read("*all")会读取当前输入文件的所有内容,以当前位置作为开始。
如果当前位置处于文件的末尾,或者文件为空,那么该调用会返回一个空字符串;
调用io.read("*line")会返回当前文件的下一行,但并不包括换行符。当到达文件末尾
时,该调用会返回nil,以表示无后续行可返回。它也是read的默认模式。建议使用
"*all"一次性读取整个文件,或者按块来读取。如果只是为了迭代文件中的所有行,
那么io.lines迭代器更为合适。
for line in io.lines() do lines[#lines+1] =line end
调用io.read("*number")会从当前输入文件中读取一个数字。此时,read会返回一个数字,
而不是字符串。"*number"选项会忽略数字前面的所有空格,并且能处理像-3、+5.2、100
及-3.4e-23这样的数字格式。如果无法在当前文件位置读到一个数字,read会返回nil。
在调用read时可以指定多个选项,函数会根据每个选项参数返回相应的内容。
示例——有一个文件,其中每行有3个数字。现在要打印出每一行中最大的数字。
方法一,用一次read函数调用来读取每行的3个数字:
while true do
local n1, n2, n3 =io.read("*number", "*number", "*number")
if not n1thenbreak end
print(math.max(n1,n2, n3))
end
方法二,用"*all"来读取整个文件,然后用gmatch来提取其中内容:
local pat = "(%S+)%s+(%S)%s+(%S)%s+"
for n1,n2, n3 in string.gmatch(io.read("*all"),pat) do
print(math.max(tonumber(n1),tonumber(n2), tonumber(n3)))
end
除以上的读取模式外,还可以用一个数字n作为read的参数。此时,read会试着从输入
文件中读取n个字符。如果读不到任何字符,它会返回nil。否则会返回一个最多n个字符
的字符串。
io.read(0)是一个特殊的情况,它用于检测是否达到了文件末尾。如果还有数据可以读取,
它会返回一个空字符串,否则返回nil。
21.2 完整I/O模型
完整模型是基于文件句柄的,它等价于c语言中的流,表示一个具有当前位置的打开文件;
要打开一个文件可以使用io.open函数,它有两个参数一个是要打开的文件名,另一个是
模式字符串。模式字符串:
"r"表示读取;"w"表示写入; "a"表示追加; "b"表示打开二进制文件。
open函数会返回表示文件的新句柄。若发生错误,则返回nil,及一条错误消息和一个错误代码。
一个错误检查的典型做法是;
local f = assert(io.open(filename, mode))
如果打开失败,错误消息就会成为assert的第二个参数,然后assert会显示这个信息。
打开文件后,就可以用read/write方法读写文件了。但要使用冒号语法,将它们作为文件
句柄的方法来调用。--> local t = f:read("*all") f:close()
I/O库提供了3个预定义C语言流的句柄:io.stdin、io.stdout和io.stderr。这样就可以将信息直接
写到错误流:io.stderr:write(message)
用户可以混合使用完整模式和简单模式。通过不指定参数调用io.input(),可以得到当前输入文件
的句柄。而通过io.input(handle),可以设置当前输入文件的句柄。
21.2.1 性能诀窍
在读取大文件时为了避免在行中间断开,只需在读一个块是再加上一行:
local lines, rest = f:read(BUFSIZE, "*line")
变量rest包含了被块所断开的那行的剩余部分。这样就可以将块与行的剩余部分连接起来,
从而得到一个总是起止于行边界上的块。
示例——统计文件中字符数、单词数和行数的程序:
local BUFSIZE = 2^13 --8k
local f = io.input(arg[1]) -- 打开输入文件
local cc, ic, wc = 0, 0, 0
while true do
local lines,rest = f:read(BUFSIZE, "*line")
if not linesthen break end
if rest thenlines = lines .. rest .. "\n" end
cc = cc +#lines -- 行数
local _, t =string.gsub(lines, "%S+", "")
wc = wc + t --单词数
_, t =string.gsub(lines, "\n", "\n")
lc = lc + t --换行符数
end
print(lc, wc, cc)
21.2.3 其它文件操作
函数tmpfile会返回一个临时文件的句柄,这个句柄是以读/写方式打开。这个文件会在程序结束时自动删除。
函数flush会将缓冲中的数据写入文件。它与write函数一样,将其作为一个函数调用时,
io.flush()会刷新当前输出文件;而将其作为一个方法调用时,f:flush()会刷新某个特定的文件f。
函数seek可以获取和设置一个文件的当前位置。它的一半形式是f:seek(whence,offset),
其中whence参数是一个字符串,指定了如何解释offset参数。它的有效值包括:
"set",offset解释为相对于文件起始的偏移量;
"cur",offset解释为相对于当前位置的偏移量;
"end",offset解释为相对于文件末尾的偏移量;
函数的返回值,总是返回文件的当前位置相对于文件起始处的偏移字节数。
whence参数的默认值是"cur",offset的默认值是0。因此调用file:seek()不会改变文件的
当前位置,并返回当前的文件位置。调用file:seek("set")会将当前位置重置为文件的起始
处并返回0.调用file:seek("end")会将当前位置设置到文件的末尾,并返回文件的大小。
如果发生错误,所有这些函数都会返回nil和一条错误消息。