Programming in Lua, 2Nd Edition - Chapter 21: The I/O Library

 

 

 

 

 

Chapter 21: The I/O Library

 

 

21.1 The Simple I/O Model

 

 

io.write 函数接受任意数量的参数,并将它们写入当前output file

 

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

--> sin (3) = 0.14112000805987

 

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

--> sin (3) = 0.1411

 

io.write(string.format("sin (3) = %.4f/n", 3.14159))  -- 四舍五入

--> sin (3) = 3.1416

 

 

io.write(a,b,c) io.write(a..b..c) 的效果一样,而且避免了连接操作。

 

不同于printwrite 不会自动添加如tabs 或换行符

 

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

--> hello Lua

--> Hi

 

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

--> helloLuaHi

 

 

print 总是使用standard output,而wirte 使用current output


 

print 会对其参数自动应用tostring

 

print({}) -->table: 003CAB88

print(nil) -->nil

 

 

io.read curren input file strings传给它的参数控制其读的内容

 

*all                  读整个文件

*line      读下一行

*number   读一个数

num       读一个string,最多读num 字符

 

io.read("*all") 从当前位置读整个current input file。如果当前位置处于文件的尾部,或文件是空文件,这个调用返回空串。

 

因为Lua 处理长strings 非常高效,一个实现string过滤器的简单技术是从文件读整个串,处理这个串(典型的是使用gsub 进行全局替换),然后将串写入output

 

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 是字符的十六进制数码。为保持一致性,字符“=”必须被妥善处理。

 

将文件用MIME 编码

 

t = io.read("*all")

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

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

  end)

io.write(t)

 

"([/128-/255=])" 这个模式捕获所有值为128 255 的字符和“=”字符。

 

io.read("*line") 返回current input file 的下一行,并且不带换行符。当到达文件尾,io.read("*line") 返回nil通常,仅当程序自然的需要一行一行处理文件的时侯才使用io.read("*line");否则,我更喜欢使用io.read("*all")一次读取整个文件。

 

将当前input 加上行号拷贝到当前output

 

for count = 1, math.huge do

       local line = io.read()

       if line == nil then break end

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

end

 

但是,对文件进行一行一行的迭代,我们最好使用io.lines 迭代器

 

local lines = {}

-- read the lines in table 'lines'

for line in io.lines() do lines[#lines + 1] = line end

-- sort

table.sort(lines)

-- write all the lines

for _, l in ipairs(lines) do io.write(l, "/n") end

 

io.read("*number") curren input file 返回一个数字,这是read 返回数字而不是string 的唯一情形。当一个程序需要从一个文件读许多数,没有作为中间结果的strings 可以改善程序性能。“*number”选项跳过number 前的所有空白,并且接受像这样的数字格式: -3, +5.2, 1000, -3.4e-23。如果它在当前文件位置不能找到一个数字(因为错误的格式或到达文件尾部),就返回nil

 

假设你有一个文件,每行三个数:

 

6.0 -3.23 15e12

4.3 234 1000001

...

 

现在你想打印每行的最大值,你可以只用一行代码读取所有三个数:

 

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

 

无论如何,你应当总是考虑读取整个文件的方法,然后用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

 

"(%S+)%s+(%S+)%s+(%S+)%s+" 表示匹配一个或多个不是空白的字符 + 一或多空白 +不是一或多空白

 

 

read(n) input 文件读取n 个字符。如失败返回nil;否则,返回最多n 个字符的串。

 

下面的例子在lua 中是一种非常高效的做法:

 

while true do

       local block = io.read(2^13) -- buffer size is 8K

       if not block then break end

       io.write(block)

end

 


 

做为一个特殊的例子,io.read(0) 返回空串,if there is more to be read or nil otherwise

 

 

21.2 The Complete I/O Model

 

io.open  读用“r”,写用“w”(写入前擦除原文件),appending 用“a”,还可以加上一个“b”表示打开的是二进制文件。

 

io.open 如成功则返回文件句柄,失败则返回nil,加上错误消息和错误号。错误号是系统相关的。

 

print(io.open("non-existent-file", "r"))

--> nil non-existent-file: No such file or directory 2

print(io.open("/etc/passwd", "w"))

--> nil /etc/passwd: Permission denied 13

 

f = io.open("c://input.txt","r") -- open input file

assert(f)

 

典型地检查错误用法:

 

local f = assert(io.open(filename, mode))

 

从文件逐行读3 个数并输出最大值

 

-- input.txt

6.0 -3.23 15e12

6.0 -3.23 15e12

4.3 234 1000001

----

local f = assert(io.open("c://input.txt", "r"))

local s = f:read("*all")

local pat = "(%S+)%s+(%S+)%s+(%S+)%/n-"

for n1, n2, n3 in string.gmatch(s, pat) do

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

end

f:close()


 

I/O 库提供三个预定义的C 流:io.stdinio.stdout,和io.stderr。所以,你可以直接发送一个消息到错误流:

 

io.stderr:write(message)

 

我们可以混合complete model simple model。我们不带参数调用io.input() 获得当前文件句柄。设置文件句柄用io.input(handle)。(类似的调用对io.output 也有效

例如,如果你想临时改变current input file,你可以写类似这样的代码:

 

Lua 的“文件重定向”

 

local temp = io.input() -- save current file

io.input("c://input.txt") -- open a new current file

 

local pat = "(%S+)%s+(%S+)%s+(%S+)%/n-"

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

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

end

 

io.input():close() -- close current file

io.input(temp) -- restore previous current file

 

 

A small performance trick

 

通常,在lua 中一次读整个文件比一行一行读取要快。但是,有时我们必须面对大文件(意思是,数十或数百兆),这种情况下不可能一次读取整个文件。如果你想要在处理这种大文件时获得最大性能,最快的方法是以每次“一大块”(例如 8K 每块)的方式读取大文件。为避免中间断行的问题,只需这样做:

 

local lines, rest = f:read(BUFSIZE, "*line")

 

变量rest 将获得当一行中没被读完时剩下的部分大小。我们只需将这两部分连起来组成完整的一行。

 


 

输出文件的单词数、行数和字符数(用全局替换函数来计算)

 

local BUFSIZE = 2^13 -- 8K

--local f = io.input(arg[1]) -- open input file

local f = io.input("c://input.txt") -- open input file

local cc, lc, wc = 0, 0, 0 -- char, line, and word counts

while true do

       local lines, rest = f:read(BUFSIZE, "*line")

       if not lines then break end

       if rest then lines = lines .. rest .. "/n" end

       cc = cc + #lines

       -- count words in the chunk

       local _, t = string.gsub(lines, "%S+", "")

       wc = wc + t

       -- count newlines in the chunk

       _,t = string.gsub(lines, "/n", "/n")

       lc = lc + t

end

print(lc, wc, cc)

 

 

Binary files

 

简单模式函数io.input io.output 总是默认以文本模式打开文件。Unix 中,二进制文件和文本文件没差别但在一些系统,特别是windows,二进制文件必须以特殊flag 打开。为处理这种二进制文件,你必须使用io.open,并在mode string 中加上一个字符“b


 

Lua 中处理二进制数据和文本数据是差不多的。一个strings 可以包含任意bytes,并且几乎所有库函数都可以处理任意bytes你甚至可以对二进制数据进行模式匹配,只要数据中不含一个zero byte。如果你想要匹配这个byte,你可以使用“%z”代替。

 

通常使用“*all”读整个文件,或是使用参数n,读n bytes。作为一个简单例子,下面的程序将一个文本文件从Dos 格式转换成Unix 格式。(就是将“回车 换行”转成“换行”)。

 

 

将文件从Dos 格式转换成Unix 格式

 

local inp = assert(io.open("c://input.txt", "rb"))

local out = assert(io.open("c://output.txt", "wb"))

local data = inp:read("*all")

data = string.gsub(data, "/r/n", "/n")

out:write(data)

assert(out:close())

 

 

打印二进制文件中找到的所有string

 

-- 把下面的文本文件的回车0x0D,换行符0x0A 全部用十六进制编缉器用0x00 替换掉

-- input.txt

6.0 -3.23 15e12

6.0 -3.23 15e12

4.3 234 1000001

----

local f = assert(io.open("c://input.txt", "rb"))

local data = f:read("*all")

local validchars = "[%w%p%s]"

local pattern = string.rep(validchars, 6) .. "+%z"

for w in string.gmatch(data, pattern) do

       print(w)

end

 

上面的代码假定一个string 以任意0x00 结尾的六个或更多个有效字符序列,所谓有效是指能被模式pattern 接受的。%z”匹配byte zero

 


 

将文件以“十六进制编缉器”的格式打印

 

local f = assert(io.open("c://input.txt", "rb"))

local block = 16

while true do

       local bytes = f:read(block)

       if not bytes then break end

       for _, b in pairs{string.byte(bytes, 1, -1)} do

              io.write(string.format("%02X ", b))

       end

       io.write(string.rep(" ", block - string.len(bytes))) -- 前面的输出不足十六个的部分用空格补

       io.write(" ", string.gsub(bytes, "%c", "."), "/n")   -- 将控制字符(%c control characters)转换成点

end

 

输出二进制文件中每个byte 的十六进制表示,接着在后面输出这个byte 的文本表示,然后将控制字符(%c control characters)转换成点“.”。注意这个{string.byte(bytes,1,-1)} 的使用。

 

 

21.3 Other Operations on Files

 

tmpfile 函数返回一个临时文件的句柄,以读写的模式打开。当你的程序结束时,这个文件会自动被删除。flush 函数将所有还没有写入文件的内容立既写入文件。像write 函数一样,你可以这样调用io.flush()flush current output file;或是这样调用f:flush()flush 到一个特定文件。


 

seek 函数可以同时获得或设置一个文件的当前位置。通常形式为f:seek(whence,offset)whence 参数是一个string,指定如何去解释偏移。file:seek("set") 将文件定位到头部,并返回0file:seek("end") 将文件定位到尾部,并返回文件的大小。

 

获得文件大小,而且不改变文件的当前位置

 

local f = assert(io.open("c://input.txt", "rb"))

function fsize (file)

       local current = file:seek() -- get current position

       local size = file:seek("end") -- get file size

       file:seek("set", current) -- restore position

       return size

end

print(fsize(f))

f:close()

 

这些函数出错时都返回nil 加上错误消息

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值