第二十一课 I/O库

I/O库为文件操作提供了两种不同的模型,简单模型和完整模型。简单模型假设有一个当前输入文件和一个当前输出文件,它的I/O操作均作用于这些文件。完整模型则使用 显式的文件句柄。它采用了面向对象的风格,并将所有的操作定义为文件句柄上的方法。

简单I/O模型
简单模型的所有操作都作用于两个当前文件。I/O库将当前输入文件初始化为进程标准输入,将当前输出文件初始化为进程标准输出。在执行io.read()操作时,就会从标准输入中读取一行。
用函数 io.input和io.output可以改变这两个当前文件。io.input(filename)调用会以只读模式打开指定的文件,并将其设为当前输入文件,之后除非再次调用io.input,否则所有的输入 都将来源于这个文件。在输出方面,io.output也可以完成类似的工作。如果出现错误,这两个函数都会引发(raise)错误。如果想直接处理这些错误,则必须使用完整模型中的io.open。
通常write比read简单些,首先看一下write。函数io.write接收任意数量的字符串参数,并将它们写入当前输出文件。它也可以接受数字参数,数字参数会根据常规的转换规则转换为字符串。如果想要完全地控制这种转换,则应该使用函数string.format:
io.write("sin(3) = ", math.sin(3), "\n")
io.write(string.format("sin(3) = %.4f\n", math.sin(3)))
在实际操作中应当避免写出io.write(a..b..c)这样的代码,而是应该直接调用io.write(a, b, c)。
print("Hello", "Lua"); print("Hi")
-->Hello Lua
-->Hi
io.write("Hello", "Lua"); io.write("Hi", "\n")
-->HelloLuaHi
write和print有几点不同:
1、write在输出时不会添加像制表符或者回车这样的额外字符。
2、write使用当前输出文件,而print总是使用 标准输出。
3、print会自动调用其参数的tostring()方法,因此它还能 显示table、函数和nil。
函数io.read从当前输入文件中读取字符串,它的参数决定了要读取的数据:
*all 读取整个文件
*line 读取下一行,默认模式
*number 读取一个数字
<num> 读取一个不超过<num>个字符的字符串,io.read(0)是一个特殊 情况,它用于检查是否到达了文件末尾。如果还有数据 可以读取,它会返回一个空字符串,否则返回nil。

完整I/O模型
若要作更多的I/O控制,可以使用完整模型。这个模型是基于文件句柄的,它等价于C语言中的流(FILE*),表示一个具有当前位置的打开文件。
io.open(filename, MODE) 打开一个文件,其中MODE支持:
“r”读取,“w”写入(同时会删除文件原来的 内容),“a”追加,“b”打开二进制文件。open函数会返回表示文件的新句柄。若发生错误,则返回nil,以及一条错误消息和一个错误代码。
print(io.open("non-existent-file", "r")) -->nil non-existent-file:No such file or directory 2
一个错误检查的典型做法是:
local f = assert(io.open(filename, mode))
如果打开失败,错误消息就会成为assert的第二个参数, 然后assert会显示这个信息。
打开一个文件后,就可以用read/write方法读写文件了。这与read/write函数相似,但是需要用冒号 语法,将它们作为文件句柄的方法来调用。例如:
local f = assert(io.open(filename, "r"))
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),可以设置当前输入文件的句柄。例如,要临时改变当前输入文件:
local temp = io.input()
io.input("newinput")
<some operation>
io.input():close()
io.input(temp)

性能小诀窍
通常在Lua中,一次性读取整个文件 比逐行地读取要快一些。但必须处理一些 大文件( 几十兆或几百兆字节)时,就无法一次性地读取所有的内容。如果希望以最高性能来处理这种大文件, 那么最快的方法就是用足够大的块(例如8KB大小的块)来读取文件。为了避免在行中间断开,只需在读一个块时再加上一行:
local lines, rest = f:read(BUFSIZE, "*line")
变量rest包含了被块所断开的那行的剩余部分。这样就可以将块与行的剩余部分连接起来,从而得到一个总是起止于行边界上的块。
local BUFFSIZE = 2^13 --8K
local f = io.input(arg[1])
local cc, lc, wc = 0, 0, 0 --字符、行、单词的计数
while true do
local lines, rest = f:read(BUFFSIZE, "*line")
if not lines then break end
if rest then lines = 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)

二进制文件
简单模式中的函数 io.input和io.output总是以 文本方式打开文件(默认行为)。在UNIX中,二进制文件和文本文件是没有 差别的。但是在其他一些系统中,特别是在 Windows中,必须用特殊的标识来打开二进制文件。在处理二进制文件时,io.open的模式字符串中必须带有字母“b”。

其他文件操作
函数 tmpfile返回一个临时文件的句柄,这个句柄是以读写方式打开。这个文件会在程序时自动删除。函数flush会将 缓冲中的数据写入文件。它与write函数一样,将其作为一个函数调用时,io.flush会刷新当前输出文件;而将其 作为一个方法调用时, f:flush会刷新某个特定的文件f。
函数seek可以获取和设置一个文件的当前位置。它的一般形式是f:seek(whence, offset),其中whence参数是一个字符串,指定了如何解释offset参数。函数的返回值与whence无关,它总是返回文件的当前位置,即相对于文件 起始处的偏移字节数。whence参数的默认值是cur,offset的默认值是0。
whence:
set 相对于文件起始的偏移量
cur 相对于当前位置的偏移量
end 相对于文件末尾的偏移量
调用file:seek()不会改变文件的当前位置,并返回当前的文件位置。
调用file:seek("set")会将当前位置重置为文件的起始处(并返回0)。
调用file:seek("end")会将当前位置设置到文件的末尾,并返回文件的大小。
function fsize (file)
local current = file:seek() --获取当前位置
local size = file:seek("end") --获取文件大小
file:seek("set", current) --恢复位置
return size
end
如果发生错误,所有这些函数都会返回nil和一条错误消息。










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值