13.5 I/O库 (I/O Library) Lua 标准库中的 I/O 库详解:代码实践与应用 文件的基本读写操作: , , , 文件 I/O 是程序与持久化存储交互的核心方式。Lua 的 库提供了 函数来打开文件,并返回一个文件句柄 (file handle),通过这个句柄,我们可以进行读取和写入操作。 1.1 : 打开文件 函数用于打开指定 的文件。它接受两个参数: : 要打开的文件名,字符串类型。 : 打开模式,可选参数,字符串类型。默认为 "r" (只读)。 参数定义了文件的打开方式,常见的模式如下: "r": 只读模式 (read mode)。文件必须存在,如果不存在则打开失败。 "w": 写入模式 (write mode)。如果文件存在,则清空文件内容;
io.open, file:read, file:write, file:close文件 I/O 是程序与持久化存储交互的核心方式。Lua 的 io 库提供了 io.open 函数来打开文件,并返回一个文件句柄 (file handle),通过这个句柄,我们可以进行读取和写入操作。
io.open(filename [, mode]): 打开文件io.open 函数用于打开指定 filename 的文件。它接受两个参数:
filename: 要打开的文件名,字符串类型。
mode: 打开模式,可选参数,字符串类型。默认为 "r" (只读)。
mode 参数定义了文件的打开方式,常见的模式如下:
"r": 只读模式 (read mode)。文件必须存在,如果不存在则打开失败。
"w": 写入模式 (write mode)。如果文件存在,则清空文件内容;如果文件不存在,则创建新文件。
"a": 追加模式 (append mode)。如果文件存在,则在文件末尾追加内容;如果文件不存在,则创建新文件。
"r+": 读写模式 (read/update mode)。文件必须存在,可以进行读写操作。
"w+": 读写模式 (write/update mode)。如果文件存在,则清空文件内容;如果文件不存在,则创建新文件,可以进行读写操作。
"a+": 追加读写模式 (append/update mode)。可以读取文件内容,写入内容总是追加到文件末尾。
"b": 二进制模式 (binary mode)。添加到模式字符串末尾,例如 "rb", "wb"。在二进制模式下,不进行行尾转换,适用于处理非文本文件。
io.open 函数返回两个值:
文件句柄 (file handle): 如果文件成功打开,返回一个表示打开文件的对象。
错误信息 (error message): 如果文件打开失败 (例如文件不存在,权限不足等),返回 nil 和一个描述错误的字符串。
代码实践 1:打开文件并检查错误
local file, err = io.open("example.txt", "r") -- 尝试以只读模式打开 "example.txt" if not file then print("打开文件失败:", err) -- 如果 file 为 nil,则打印错误信息 else print("文件打开成功!") file:close() -- 即使只打开不操作,也应该养成关闭文件的习惯 end
file:read(...): 读取文件内容file:read 是文件句柄的方法,用于从文件中读取数据。它可以接受不同的参数来控制读取的内容和格式:
"*a" (或不带参数): 读取整个文件内容,返回一个包含文件所有内容的字符串。
"*l": 读取下一行,返回一个字符串,不包含行尾符。如果到达文件末尾,返回 nil。
"*n": 读取一个数字,返回一个数字。如果无法读取数字 (例如遇到非数字字符或文件末尾),返回 nil。
num: 读取最多 num 个字符,返回一个字符串。如果文件剩余字符数少于 num,则读取所有剩余字符。如果到达文件末尾,返回空字符串 ""。
代码实践 2:读取文件内容的不同方式
-- 创建一个示例文件 example.txt local file_write = io.open("example.txt", "w") file_write:write("第一行文本\n第二行文本\n第三行文本\n123 456 789\n") file_write:close() local file_read = io.open("example.txt", "r") -- 读取整个文件 local content_all = file_read:read("*a") print("整个文件内容:\n", content_all) file_read:seek("set") -- 重置文件指针到文件开头 -- 逐行读取 print("\n逐行读取:") local line1 = file_read:read("*l") print("第一行:", line1) local line2 = file_read:read("*l") print("第二行:", line2) file_read:seek("set") -- 再次重置文件指针 -- 读取数字 print("\n读取数字:") file_read:read("*l") -- 跳过前三行 file_read:read("*l") file_read:read("*l") local num1 = file_read:read("*n") print("第一个数字:", num1) local num2 = file_read:read("*n") print("第二个数字:", num2) file_read:seek("set") -- 再次重置文件指针 -- 读取指定字符数 print("\n读取指定字符数:") local first_5_chars = file_read:read(5) print("前5个字符:", first_5_chars) local next_10_chars = file_read:read(10) print("接下来10个字符:", next_10_chars) file_read:close()
代码输出:
整个文件内容: 第一行文本 第二行文本 第三行文本 123 456 789 逐行读取: 第一行: 第一行文本 第二行: 第二行文本 读取数字: 第一个数字: 123 第二个数字: 456 读取指定字符数: 前5个字符: 第一行文 接下来10个字符: 本 第二行文本
file:write(...): 写入文件内容file:write 是文件句柄的方法,用于向文件中写入数据。它可以接受多个参数,每个参数都会被转换为字符串并写入文件。与 io.write 不同,file:write 不会自动添加换行符。
代码实践 3:写入文件
local file_write = io.open("output.txt", "w") -- 以写入模式打开文件 if not file_write then print("打开文件失败:", err) else file_write:write("这是第一行写入的内容。\n") -- 写入字符串,手动添加换行符 file_write:write("这是第二行,", "使用多个参数写入。\n") -- 写入多个参数 file_write:write(123, " ", 456, "\n") -- 写入数字和空格 file_write:close() print("内容已写入到 output.txt") end
output.txt 文件内容:
这是第一行写入的内容。 这是第二行,使用多个参数写入。 123 456
file:close(): 关闭文件file:close 是文件句柄的方法,用于关闭文件。关闭文件会释放系统资源,并将缓冲区中的数据写入磁盘。 务必在文件操作完成后关闭文件,这是一个良好的编程习惯。
代码实践 4:确保文件关闭
在前面的所有示例中,我们都使用了 file:close() 来关闭文件。 即使程序发生错误,也应该确保文件被关闭。可以使用 pcall 或 xpcall 配合 file:close() 来处理异常情况,但对于简单的示例,直接调用 file:close() 即可。
io.stdin, io.stdout, io.stderr, io.input, io.output, io.write, io.read除了文件操作,Lua 的 io 库还提供了与标准输入、标准输出和标准错误流交互的功能,以及设置默认输入输出流的能力。
io.stdin, io.stdout, io.stderrio.stdin: 表示标准输入流,通常连接到键盘输入。
io.stdout: 表示标准输出流,通常连接到控制台输出。
io.stderr: 表示标准错误流,通常也连接到控制台输出,用于输出错误信息。
这些对象是预先打开的文件句柄,可以直接使用 read 和 write 方法进行操作。
代码实践 5:使用标准流
io.stdout:write("请输入你的名字: ") -- 使用标准输出流提示用户输入 local name = io.stdin:read("*l") -- 使用标准输入流读取用户输入 if name then io.stdout:write("你好,", name, "!\n") -- 使用标准输出流输出问候语 else io.stderr:write("读取输入失败。\n") -- 使用标准错误流输出错误信息 end
io.input([file]), io.output([file])io.input([file]): 设置或获取默认的输入流。
如果提供了 file 参数 (可以是文件名或文件句柄),则将默认输入流设置为该文件。
如果不提供参数,则返回当前的默认输入流。初始时,默认输入流是 io.stdin。
io.output([file]): 设置或获取默认的输出流。
如果提供了 file 参数 (可以是文件名或文件句柄),则将默认输出流设置为该文件。
如果不提供参数,则返回当前的默认输出流。初始时,默认输出流是 io.stdout。
设置默认输入输出流后,可以使用 io.read 和 io.write 函数进行操作,而无需显式指定文件句柄。
io.write(...), io.read(...)io.write(...): 使用默认输出流写入数据。类似于 file:write,但使用默认输出流 (通常是 io.stdout)。会自动将每个参数转换为字符串并输出,不会自动添加换行符。
io.read(...): 使用默认输入流读取数据。类似于 file:read,但使用默认输入流 (通常是 io.stdin)。参数与 file:read 相同,控制读取格式。
代码实践 6:使用默认输入输出流
io.output("output_default.txt") -- 设置默认输出流为文件 "output_default.txt" io.write("使用默认输出流写入到文件。\n") -- 写入到 "output_default.txt" io.output(io.stdout) -- 恢复默认输出流为标准输出 io.input("example.txt") -- 设置默认输入流为文件 "example.txt" local line_from_file = io.read("*l") -- 从 "example.txt" 读取一行 print("从文件中读取的一行:", line_from_file) io.input(io.stdin) -- 恢复默认输入流为标准输入 io.write("请输入一些内容: ") local input_from_stdin = io.read("*l") -- 从标准输入读取一行 print("你输入的内容:", input_from_stdin)
output_default.txt 文件内容:
使用默认输出流写入到文件。
控制台输出:
从文件中读取的一行: 第一行文本 请输入一些内容: 你输入的内容 你输入的内容: 你输入的内容
string.format 与 io.write 结合虽然 io 库本身没有直接提供格式化输出的功能,但 Lua 的字符串库 string 提供了 string.format 函数,可以用于格式化字符串。结合 io.write 或 file:write,可以实现格式化输出到控制台或文件。
string.format(formatstring, ...) 函数根据 formatstring 中的格式指令,格式化后续的参数,并返回格式化后的字符串。常见的格式指令包括:
%d: 十进制整数
%x: 十六进制整数
%f: 浮点数
%s: 字符串
%c: ASCII 字符
%q: 可安全被 Lua 解释器读取的字符串
%%: 百分号自身
代码实践 7:格式化输出
local name = "张三" local age = 30 local score = 95.5 local formatted_string = string.format("姓名: %s, 年龄: %d, 分数: %.2f", name, age, score) io.write(formatted_string, "\n") local hex_num = 255 io.write(string.format("十进制: %d, 十六进制: %x\n", hex_num, hex_num))
控制台输出:
姓名: 张三, 年龄: 30, 分数: 95.50 十进制: 255, 十六进制: ff
file:flush, io.setvbuf为了提高 I/O 效率,操作系统和 Lua 都使用了缓冲机制。写入的数据通常先被写入缓冲区,当缓冲区满或显式调用刷新操作时,才会真正写入磁盘。理解和控制缓冲行为对于某些应用场景非常重要。
file:flush(): 刷新缓冲区file:flush 方法用于立即将文件句柄的写缓冲区中的所有数据写入到磁盘或底层输出流。这可以确保数据及时写入,避免数据丢失或延迟。
代码实践 8:刷新缓冲区
local file_buffered = io.open("buffered_output.txt", "w") file_buffered:write("这是一行缓冲写入的数据。\n") print("数据已写入缓冲区,但可能尚未写入磁盘。") file_buffered:flush() -- 显式刷新缓冲区 print("缓冲区已刷新,数据应该已写入磁盘。") file_buffered:close()
在上面的例子中,file:flush() 确保在程序结束前,"这是一行缓冲写入的数据。\n" 确实被写入到 buffered_output.txt 文件中。
io.setvbuf(file [, mode [, size]]): 设置缓冲模式io.setvbuf 函数用于设置文件句柄的缓冲模式。它可以接受三个参数:
file: 要设置缓冲模式的文件句柄。
mode: 缓冲模式,字符串类型,可选参数。可以是以下值:
"no": 无缓冲 (no buffering)。每次写入操作都直接写入磁盘或底层输出流。效率较低,但数据实时性高。
"full": 全缓冲 (full buffering)。缓冲区满时才进行写入操作。效率较高,但数据实时性较低。
"line": 行缓冲 (line buffering)。遇到换行符或缓冲区满时进行写入操作。适用于文本文件,兼顾效率和一定的实时性。
默认为 "full" (对于文件) 或 "line" (对于交互式设备,如终端)。
size: 缓冲区大小,数字类型,可选参数。以字节为单位。如果未指定,则使用系统默认大小。
代码实践 9:设置缓冲模式
local file_nobuffered = io.open("nobuffered_output.txt", "w") io.setvbuf(file_nobuffered, "no") -- 设置为无缓冲模式 file_nobuffered:write("无缓冲写入:每次写入都直接写入磁盘。\n") file_nobuffered:close() local file_linebuffered = io.open("linebuffered_output.txt", "w") io.setvbuf(file_linebuffered, "line") -- 设置为行缓冲模式 file_linebuffered:write("行缓冲写入:遇到换行符或缓冲区满时写入。\n") file_linebuffered:close() local file_fullbuffered = io.open("fullbuffered_output.txt", "w") io.setvbuf(file_fullbuffered, "full", 1024) -- 设置为全缓冲模式,缓冲区大小为 1024 字节 for i = 1, 10 do file_fullbuffered:write(string.format("全缓冲写入:第 %d 行。\n", i)) end file_fullbuffered:close() -- 关闭文件时,全缓冲数据会被写入
通过设置不同的缓冲模式,可以根据应用场景的需求,平衡 I/O 效率和数据实时性。对于日志文件等需要实时写入的场景,可以使用无缓冲或行缓冲模式。对于批量数据写入等对实时性要求不高的场景,可以使用全缓冲模式以提高效率。
file:seek, file:position, io.linesLua 的 io 库还提供了文件定位功能,允许程序在文件中自由移动读写位置。同时,io.lines 函数提供了一种方便的方式来迭代文件的每一行。
file:seek([whence [, offset]]): 文件定位file:seek 方法用于设置或获取文件句柄的当前读写位置 (文件指针)。它可以接受两个可选参数:
whence: 起始位置,字符串类型,可选参数。可以是以下值:
"set": 从文件开头开始计算偏移量。
"cur": 从当前位置开始计算偏移量。
"end": 从文件末尾开始计算偏移量。
默认为 "cur"。
offset: 偏移量,数字类型,可选参数。默认为 0。
file:seek 函数返回最终的文件指针位置 (相对于文件开头,以字节为单位)。如果不带参数调用,则只返回当前文件指针位置,不改变位置。
代码实践 10:文件定位
local file_seek = io.open("seek_example.txt", "w+") -- 以读写模式打开文件 file_seek:write("0123456789ABCDEF\n") -- 写入示例数据 print("初始位置:", file_seek:seek()) -- 获取当前位置 (默认为文件开头,返回 0) file_seek:seek("set", 5) -- 从文件开头偏移 5 个字节,定位到 '5' 的位置 print("定位到 'set', 5 后位置:", file_seek:seek()) file_seek:write("XXXXX") -- 从当前位置写入 "XXXXX",覆盖 '56789' print("写入 'XXXXX' 后位置:", file_seek:seek()) file_seek:seek("cur", -3) -- 从当前位置向前偏移 3 个字节,定位到 'X' 的位置 (倒数第三个 X) print("定位到 'cur', -3 后位置:", file_seek:seek()) file_seek:seek("end", -5) -- 从文件末尾向前偏移 5 个字节,定位到 'D' 的位置 print("定位到 'end', -5 后位置:", file_seek:seek()) file_seek:close() -- 读取修改后的文件内容 local file_read_seek = io.open("seek_example.txt", "r") local content_after_seek = file_read_seek:read("*a") print("\n修改后的文件内容:\n", content_after_seek) file_read_seek:close()
控制台输出:
初始位置: 0 定位到 'set', 5 后位置: 5 写入 'XXXXX' 后位置: 10 定位到 'cur', -3 后位置: 7 定位到 'end', -5 后位置: 12 修改后的文件内容: 01234XXXXXABCDEF
file:position(): 获取当前文件位置 (Lua 5.4+)在 Lua 5.4 及更高版本中,file:position() 方法被添加到 io 库,用于更清晰地获取当前文件指针位置。它等价于不带参数调用 file:seek(),但语义上更明确,推荐使用 file:position() 来获取位置,使用 file:seek() 来设置位置。
代码实践 11:使用 file:position() (Lua 5.4+)
local file_position = io.open("position_example.txt", "w+") file_position:write("Example Text\n") file_position:seek("end") -- 定位到文件末尾 local current_position = file_position:position() -- 获取当前位置 print("当前文件位置 (Lua 5.4+):", current_position) file_position:close()
io.lines(filename, ...) : 行迭代器io.lines 函数返回一个迭代器,用于逐行读取指定文件的内容。它可以接受一个可选的文件名参数。如果提供了文件名,io.lines 会打开文件并返回迭代器。如果没有提供文件名,io.lines 会使用当前的默认输入文件 (由 io.input() 设置)。
代码实践 12:使用 io.lines 迭代文件行
-- 假设 example.txt 文件已存在 (在代码实践 2 中创建) print("使用 io.lines 迭代 example.txt 文件:") for line in io.lines("example.txt") do -- 迭代文件的每一行 print("行内容:", line) end print("\n使用 io.lines 迭代标准输入 (输入 'end' 结束):") for line in io.lines() do -- 迭代标准输入 if line == "end" then break end print("你输入的行:", line) end
控制台输出 (部分,取决于 example.txt 内容和标准输入):
使用 io.lines 迭代 example.txt 文件: 行内容: 第一行文本 行内容: 第二行文本 行内容: 第三行文本 行内容: 123 456 789 使用 io.lines 迭代标准输入 (输入 'end' 结束): 你输入的行: 第一行标准输入 你输入的行: 第二行标准输入 你输入的行: end
io.lines 提供了一种简洁高效的方式来处理文本文件,尤其是在需要逐行处理文件内容时,避免了手动循环和 file:read("*l") 的繁琐操作。
本文详细介绍了 Lua 标准库 io 库中五个关键的 I/O 功能模块:
文件基本读写 (io.open, file:read, file:write, file:close): 文件操作的基础,掌握文件的打开、读取、写入和关闭是进行文件 I/O 的前提。
标准输入输出流 (io.stdin, io.stdout, io.stderr, io.input, io.output, io.write, io.read): 程序与用户交互和基本输出的重要手段,理解标准流和默认流的设置对于构建交互式程序至关重要。
格式化输出 (string.format 与 io.write 结合): 通过 string.format 格式化字符串,结合 io.write 实现美观和结构化的输出。
文件缓冲与刷新 (file:flush, io.setvbuf): 理解缓冲机制并学会控制缓冲行为,可以优化 I/O 性能,并确保数据及时写入。
文件定位与迭代 (file:seek, file:position, io.lines): 文件定位允许程序在文件中灵活移动读写位置,io.lines 则提供了方便的行迭代方式,简化了文本文件处理。
掌握这些 I/O 库的功能和用法,可以帮助 Lua 开发者有效地进行文件操作、用户交互和数据处理,构建功能完善的 Lua 应用程序。在实际应用中,应根据具体需求选择合适的 I/O 方法,并注意文件操作的错误处理和资源管理,确保程序的健壮性和可靠性。