快速掌握Lua 5.3 —— I/O库

Posted VermillionTear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速掌握Lua 5.3 —— I/O库 相关的知识,希望对你有一定的参考价值。

Q:什么是”Complete Model”?

A:所有的文件操作都基于明确指定的文件句柄,可以同时打开多个文件句柄。这就意味着同一时间可以操作多个文件,对于每一个文件读或写均可。文件句柄等同于C语言中的”FILE*”,它代表一个被打开文件的当前读取位置。io.open()可以指定打开的文件,并返回其文件句柄,

--[[ io.open(filename [, mode])
     以"mode"模式打开文件"filename",返回其文件句柄。
     "mode"有以下选项,功能与C语言中的"fopen()"的"mode"参数功能相同:
     "r": 以只读方式打开,文件不存在时报错(不指定时,默认使用此选项);
     "w": 以只写方式打开,若文件存在则清空文件,否则创建文件;
     "a": 以附加只写的方式打开,若文件不存在,则会创建文件,如果文件存在,
          写入的数据会被加到文件尾,即文件原先的内容会被保留(原来的EOF符保留);
     "r+": 以可读写方式打开,文件不存在时报错;
     "w+": 以可读写方式打开,若文件存在则清空文件,否则创建文件;
     "a+": 以附加可读写的方式打开,若文件不存在,则会创建文件,如果文件存在,
           写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的EOF符不保留);
     当出错时返回"nil"+错误信息+错误码。]]
print(io.open("file", "r"))    -- "file"文件不存在。
--> nil    file: No such file or directory    2
local f = io.open("file", "w+")    -- 创建"file"文件。
f:write("Hello World!")
f:close()
--[[ "file"文件中:
     Hello World!]]

print(io.open("/etc/passwd", "w"))
--> nil    /etc/passwd: Permission denied    13

Q:如何操作文件句柄?

A:之前的例子已有涉及,我们可以通过文件句柄直接调用”read”,”write”等函数,他们的功能对应各自的io.*()形式的函数,因为io.*()形式的函数实际上调用的是io.input():*()

local f = io.open("file", "w+")
--[[ 其功能等价于"io.input("file"); io.read()"。
     因为"io.read()"实际上调用的是"io.input():read()"。]]
f:read()
for l in f:lines() do     -- "file:lines()"不同于"io.lines()",在循环结束时不会关闭文件句柄。
    -- do something
end
f:write("something")
f:flush()    -- 强制将写入缓存保存到文件中。
--[[ file:setvbuf(mode [, size])
     设置写入文件的数据的缓冲模式。"mode"可以设置为以下值:
     "no": 不缓冲。写入数据直接存储到文件中。
     "full": 全缓冲。只有在缓存满或是显式的对文件调用"flush()"时,数据才写入到文件。
     "line": 行缓冲。当遇到换行时将数据写入文件(对于某些特殊文件,例如终端设备,是遇到任何输入前)。
     对于后两种模式,"size"以字节为单位指定缓冲区的大小。]]
--[[ file:seek([whence [, offset]])
     根据"whence"指定的模式,将文件当前读取位置偏移"offset"个字节。"whence"可指定的模式如下:
     "set": 将文件当前读取位置定位到文件开头。
     "cur": 将文件当前读取位置定位到当前位置。
     "end": 将文件当前读取位置定位到文件结尾。
     "whence"默认为"cur""offset"默认为0。函数返回文件当前读取位置距文件开头的偏移。
     此函数没有对应的"io.seek()"。]]
-- 在不改变文件读取位置的情况下获取文件的大小。
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
f:close()

Q:如何将一个文件的内容按照十六进制编辑器的样式输出?

A:

-- "a.lua"文件中的内容:
local f = assert(io.open(arg[1], "r"))
local block = 10    -- 每次转换10个字符,作为一行显示。
while true do
    local bytes = f:read(block)
    if not bytes then break end
    for b in string.gmatch(bytes, ".") do    -- 匹配任意字符。
        -- 将字符转换为十六进制的形式。
        io.write(string.format("%02X ", string.byte(b)))
    end
    --[[ 十六进制与正常的字符之间的空格("+1"为的就是这些空格)。
         "%02X "的模式正好占3个字符的位置,所以最后一行如果不足10个字符,
         每个不够的字符位置使用3个空格代替。]]
    io.write(string.rep("   ", block - string.len(bytes) + 1))
    -- 控制字符都转换为了"."输出。
    io.write(string.gsub(bytes, "%c", "."), "\n")
end

-- 以脚本本身作为输入文件。
--[[ result: 
     > lua a.lua a.lua
     6C 6F 63 61 6C 20 66 20 3D 20    local f = 
     61 73 73 65 72 74 28 69 6F 2E    assert(io.
     6F 70 65 6E 28 61 72 67 5B 31    open(arg[1
     ...
     79 74 65 73 2C 20 22 25 63 22    ytes, "%c"
     2C 20 22 2E 22 29 2C 20 22 5C    , "."), "     6E 22 29 0A 65 6E 64 0A          n").end.
]]

附加:

1、当文件为空,或者文件当前的读取位置在文件尾时,file:read()io.read()的行为相同。file:read("a")会返回空字符串,而file:read("l")和io.read(“*number”)都会返回nil。
2、

local f = io.open("file", "r")
...    -- 执行若干次"f:read()"。
print(f:seek())    -- 打印文件当前的读取位置。
print(f:seek("end", 0))    -- 打印文件大小。

3、I/O库还提供了3个预定义的标准文件句柄,io.stdinio.stdoutio.stderr。你可以使用他们直接对标准输入、标准输出和标准错误输出读或写。

print(io.stdin:read())    -- 输入什么,打印什么。
print(io.stdout:write("123"))    -- 123
print(io.stderr:write("123"))    -- 123

4、通常在Lua中,将文件作为一个整体读取会比逐行读取要快。但有些时候,我们会面对不适合我们一次性读取的巨大的文件(比如10M或100M的文件),如果你想要在读取这类文件时达到最佳性能,你可以使用一个”chunk”大小的方式读取(例如8KB)。为了防止原文件中的一行被截断,还要在io.read()中多增加一个参数*l

--[[ "file"文件中内容:
     line1 abc
     line2
     line3 def]]

-- "a.lua"文件中内容:
local BUFSIZE = 2^13    -- 8K
local f = io.input(arg[1])    -- open input file
local cc, lc, wc = 0, 0, 0    -- char, line, and word counts
while true do
    -- 首先读取一个"chunk"的大小。为了防止一行被截断,将本行中剩余部分读完。
    local lines, rest = f:read(BUFSIZE, "*L")
    if not lines then break end
    if rest then lines = lines .. rest end    -- 把被截断的一行重新拼接起来。
    cc = cc + string.len(lines)    -- 共包含多少个字符。
    local _, t = string.gsub(lines, "%S+", "")    -- 共包含多少个单词。
    wc = wc + t
    _, t = string.gsub(lines, "\n", "\n")    -- 共包含多少行。
    lc = lc + t
end
print(lc, wc, cc)    -- 其打印结果与"wc"命令的结果相同。

--[[ result: 
     > lua a.lua file
     3    5    26
     > wc file
     3    5    26    file]]

5、在类Unix的操作系统中,文本模式的文件与二进制模式的文件没有什么区别。但在Windows操作系统中可就不一样了,需要特定的标志才能正确的打开二进制文件。所以如果你的程序需要考虑跨平台的话,最好在打开二进制文件时指定”b”标志。

--[[ 一个比较实用的例子是转换Windows中的换行符"\r\n"到Unix中的换行符"\n"。
     我们不使用标准输入和标准输出,因为他们是以文本模式读取和写出数据的,
     取而代之的是使用参数指定输入输出文件。]]
local inp = assert(io.open(arg[1], "rb"))
local out = assert(io.open(arg[2], "wb"))

local data = inp:read("*a")
data = string.gsub(data, "\r\n", "\n")
out:write(data)

assert(out:close())

以上是关于快速掌握Lua 5.3 —— I/O库 的主要内容,如果未能解决你的问题,请参考以下文章

快速掌握Lua 5.3 —— 字符串库

快速掌握Lua 5.3 —— 字符串库

快速掌握Lua 5.3 —— 调试库

快速掌握Lua 5.3 —— 调试库

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧

快速掌握Lua 5.3 —— 编写提供给Lua使用的C库函数的技巧