Lua_第 20 章 IO库

第 20 章 IO库

I/O  库为文件操作提供两种模式。简单模式(simple  model)拥有一个当前输入文件和一个当前输出文件,而且提供针对这些文件相关的操作。全然模式(complete  model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将全部的文件操作定义为文件句柄的方法。简单模式在做一些简单的文件操作时较为合适。在本书的前面部分我们一直都在使用它。可是在进行一些高级的文件操作的时候,简单模式就显得力不从心。

例 如同一时候读取多个文件这种操作,使用全然模式则较为合适。I/O  库的全部函数都放在 表(table)io中。

 

20.1简单I/O 模式

       简单模式的全部操作都是在两个当前文件之上。I/O 库将当前输入文件作为标准输 入(stdin),将当前输出文件作为标准输出(stdout)。这样当我们运行 io.read,就是在标 准输入中读取一行。我们能够使用 io.input 和 io.output 函数来改变当前文件。比如 io.input(filename)就是打开给定文件(以读模式),并将其设置为当前输入文件。接下来全部的输入都来自于该文,直到再次使用 io.input。io.output 函数。类似于io.input。一旦 产生错误两个函数都会产生错误。假设你想直接控制错误必须使用全然模式中 io.read 函 数。

写操作较读操作简单。我们先从写操作入于。

以下这个样例里函数 io.write 获取任 意数目的字符串參数,接着将它们写到当前的输出文件。通常数字转换为字符串是依照 通常的规则,假设要控制这一转换,能够使用 string库中的 format 函数: 

> io.write("sin (3) =", math.sin(3), "
")
--> sin (3) = 0.1411200080598672
> io.write(string.format("sin (3) = %.4f
", math.sin(3)))
--> sin (3) = 0.1411

    在编写代码时应当避免像 io.write(a..b..c);这种书写,这同 io.write(a,b,c)的效果是 一样的。可是后者由于避免了串联操作,而消耗较少的资源。

原则上当你进行粗略(quickand dirty)编程。或者进行排错时常使用 print 函数。当须要全然控制输出时使用write。

> print("hello", "Lua"); print("Hi")
--> hello   Lua
--> Hi
 
> io.write("hello", "Lua"); io.write("Hi", "
")
--> helloLuaHi

        Write 函数与 print函数不同在于。write 不附加不论什么额外的字符到输出中去。比如制表符。换行符等等。还有 write 函数是使用当前输出文件,而 print 始终使用标准输出。 另外 print函数会自己主动调用參数的 tostring 方法,所以能够显示出表(tables)函数(functions) 和 nil。

read 函数从当前输入文件读取串,由它的參数控制读取的内容:

  

"*all" 读取整个文件
"*line" 读取下一行
"*number" 从串中转换出一个数值
num 读取 num 个字符到串

       io.read("*all")函数从当前位置读取整个输入文件。

假设当前位置在文件末尾。或者 文件为空,函数将返回空串。因为 Lua 对长串类型值的有效管理,在 Lua 中使用过滤器 的简单方法就是读取整个文件到串中去。处理完之后(比如使用函数 gsub)。接着写到 输出中去:

t = io.read("*all")        --read the whole file
t= string.gsub(t, ...)       -- do thejob 
io.write(t)                      -- write the file

       下面代码是一个完整的处理字符串的样例。文件的内容要使用 MIME(多用途的网 际邮件扩充协议)中的 quoted-printable 码进行编码。

以这样的形式编码,非 ASCII字符将 被编码为"=XX"。当中 XX是该字符值的十六进制表示。为表示一致性"="字符相同 要求被改写。

在 gsub 函数中的"模式"參数的作用就是得到全部值在 128 到 255之间的 字符。给它们加上等号标志。

t =io.read("*all")
t =string.gsub(t, "([128-255=])", function (c)
return string.format("=%02X", string.byte(c))
end)
io.write(t)

     该程序在奔腾 333MHz 环境下转换 200k 字符须要 0.2 秒。

      io.read("*line")函数返回当前输入文件的下一行(不包括最后的换行符)。当到达文 件末尾。返回值为 nil(表示没有下一行可返回)。

该读取方式是read 函数的默认方式, 所以能够简写为 io.read()。通常使用这样的方式读取文件是因为对文件的操作是自然逐行进行的。否则更倾向于使用*all一次读取整个文件,或者稍后见到的逐块的读取文件。以下的程序演示了应怎样使用该模式读取文件。

此程序复制当前输入文件到输出文件。 并记录行数。

local count = 1
while true do
  local line = io.read()
  if line == nil then break end
  io.write(string.format("%6d ", count), line, "
") 
  count = count+ 1
end<span style="font-size:14px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

然而为了在整个文件里逐行迭代。

我们最好使用 io.lines 迭代器。比如对文件的行进行排序的程序例如以下:

local lines = {}
-- read thelines in table'lines'
for line in io.lines() do
   table.insert(lines, line)
end
-- sort
table.sort(lines)
-- write allthe lines
for i, l in ipairs(lines) do io.write(l, "
") end

     在奔腾 333MHz 上该程序处理处理 4.5MB 大小,32K行的文件耗时 1.8 秒。比使用 高度优化的 C 语言系统排序程序快 0.6 秒。

io.read("*number")函数从当前输入文件里读 取出一个数值。

仅仅有在该參数下read 函数才返回数值,而不是字符串。当须要从一个文 件中读取大量数字时,数字间的字符串为空臼能够显著的提高运行性能。*number 选项 会跳过两个可被识别数字之间的随意空格。这些可识别的字符串能够是-3、+5.2、1000。 和  -3.4e-23。假设在当前位置找不到一个数字(因为格式不正确。或者是到了文件的结尾)。 则返回 nil 能够对每一个參数设置选项,函数将返回各自的结果。假如有一个文件每行包括三个数字:

 

6.0        -3.23       15e12
4.3         234        1000001
...

如今要打印出每行最大的一个数,就能够使用一次 read函数调用来读取出每行的全 部三个数字:

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

在不论什么情况下。都应该考虑选择使用 io.read 函数的 " *.all" 选项读取整个文件,然 后使用gfind 函数来分解:

local pat = "(%S+)%s+(%S+)%s+(%S+)%s+"
for n1, n2, n3 in string.gfind(io.read("*all"), pat) do
     print(math.max(n1, n2,n3))
end

       除了基本读取方式外。还能够将数值 n作为 read函数的參数。在这种情况下read 函数将尝试从输入文件里读取 n  个字符。假设无法读取到不论什么字符(已经到了文件末尾), 函数返回 nil。否则返回一个最多包括 n 个字符的串。

下面是关于该 read 函数參数的一 个进行高效文件复制的样例程序(当然是指在 Lua 中)

local size = 2^13        -- good buffersize (8K)
while true do
    local block = io.read(size)
    if not block then break end
     io.write(block)
end

特别的,io.read(0)函数的能够用来測试是否到达了文件末尾。

假设不是返回一个空串,假设己是文件末尾返回 nil。

20.2全然I/O 模式

       为了对输入输出的更全面的控制。能够使用全然模式。全然模式的核心在于文件句柄(file handle)。

该结构类似于 C 语言中的文件流(FILE*),其呈现了一个打开的文件以及当前存取位置。

打开一个文件的函数是 io.open。它模仿 C 语言中的 fopen 函数,同 样须要打开文件的文件名称參数,打开模式的字符串參数。模式字符串能够是 "r"(读模 式),"w"(写模式,对数据进行覆盖),或者是 "a"(附加模式)。

而且字符 "b" 可附加 在后面表示以二进制形式打开文件。正常情况下 open 函数返回一个文件的句柄。假设错误发生,则返回 nil。以及一个错误信息和错误代码。

print(io.open("non-existent file", "r"))
--> nil    No suchfile or directory       2
print(io.open("/etc/passwd", "w"))
--> nil    Permission denied               13

错误代码的定义由系统决定。 下面是一段典型的检查错误的代码:

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

假设 open 函数失败,错误信息作为 assert的參数。由 assert 显示出信息。文件打开 后就能够用 read 和 write 方法对他们进行读写操作。它们和 io表的 read/write 函数类似。 可是调用方法上不同,必须使用冒号字符。作为文件句柄的方法来调用。比如打开一个 文件并所有读取。

能够使用例如以下代码。

local f = assert(io.open(filename, "r"))
local t = f:read("*all") 
f:close()

同 C 语言中的流(stream)设定类似。I/O  库提供三种提前定义的句柄:io.stdin、io.stdout和 io.stderr。因此能够用例如以下代码直接发送信息到错误流(error  stream)。

io.stderr:write(message)

     我们还能够将全然模式和简单模式混合使用。

使用没有不论什么參数的 io.input()函数得 到当前的输入文件句柄;使用带有參数的 io.input(handle)函数设置当前的输入文件为 handle 句柄代表的输入文件。

(相同的使用方法对于 io.output 函数也适用)比如要实现临时的改变当前输入文件,能够使用例如以下代码:

local temp = io.input()     -- save current file
io.input("newinput")        -- open a new currentfile
...                         -- dosomething with newinput
io.input():close()          --close current file
io.input(temp)              -- restore previous current file
 


20.2.1 I/O 优化的一个小技巧

     因为通常 Lua 中读取整个文件要比一行一行的读取一个文件快的多。虽然我们有时 候针对较大的文件(几十,几百兆),不可能把一次把它们读取出来。

要处理这种文件 我们仍然能够一段一段(比如 8kb 一段)的读取它们。同一时候为了避免分割文件里的行, 还要在每段后加上一行:

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

     以上代码中的 rest 就保存了不论什么可能被段划分切断的行。然后再将段(chunk)和行 接起来。

这样每一个段就是以一个完整的行结尾的了。下面代码就较为典型的使用了这一技巧。

该段程序实现对输入文件的字符。单词,行数的计数。

 

</pre></div><pre name="code" class="csharp">local BUFSIZE = 2^13        -- 8K
local f = io.input(arg[1])  -- open inputfile
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 ..'
' end
   cc = cc +string.len(lines)
   -- count wordsin the chunk
   local _,t = string.gsub(lines,"%S+", "")
   wc = wc + t
   -- count newlinesin the chunk
   _,t = string.gsub(lines, "
", "
") 
   lc = lc + t
 end
print(lc, wc,cc)

20.2.2 二进制文件

        默认的简单模式总是以文本模式打开。

在 Unix 中二进制文件和文本文件并没有区 别,可是在如 Windows 这种系统中,二进制文件必须以显式的标记来打开文件。

控制 这种二进制文件,你必须将"b"标记加入在 io.open 函数的格式字符串參数中。

在 Lua 中二进制文件的控制和文本类似。一个串能够包括不论什么字节值。库中差点儿全部的函数都能够用来处理随意字节值。(你甚至能够对二进制的"串"进行模式比較,仅仅要串中不存在 0 值。

假设想要进行 0 值字节的匹配,你能够使用%z 取代)这样使用*all 模式就是读取整个文件的值。使用数字 n 就是读取 n 个字节的值。

下面是一个将文本文件从 DOS 模式转换到 Unix 模式的简单程序。

(这样转换过程就是将"回车换行字符"替换成"换 行字符"。)由于是以二进制形式C原稿是 Text Mode!!?

?)打开这些文件的,这里无法使用标准输入输入文件(stdin/stdout)。所以使用程序中提供的參数来得到输入、输出 文件名称。

<pre name="code" class="csharp"><pre name="code" class="csharp">local inp = assert(io.open(arg[1], "rb"))
local out = assert(io.open(arg[2], "wb"))

local data = inp:read("*all")
data = string.gsub(data, "
", "
")
out:write(data)

assert(out:close())


能够使用例如以下的命令行来调用该程序。

> luaprog.lua file.dos file.unix

      第二个样例程序:打印在二进制文件里找到的全部特定字符串。该程序定义了一种 最少拥有六个"有效字符",以零字节值结尾的特定串。

(本程序中"有效字符"定义为 文本数字、标点符号和空格符,由变量 validchars 定义。

)在程序中我们使用连接和string.rep 函数创建 validchars。以%z 结尾来匹配串的零结尾。

local f = assert(io.open(arg[1], "rb"))
local data = f:read("*all")
local validchars = "[%w%p%s]"
local pattern = string.rep(validchars, 6) .."+%z"
for w in string.gfind(data, pattern)do
    print(w)
end

最后一个样例:该程序对二进制文件进行一次值分析(得到类似于十六进制编辑器的一个界面显示。Dump)。程序的第一个參数 是输入文件名称。输出为标准输出。

其依照 10 字节为一段读取文件,将每一段各字节的十六进制表示显示出来。接着再以文本的形式写出该段,并将控制字符转换为点号。

local f = assert(io.open(arg[1], "rb"))
local block = 10
while true do
  local bytes = f:read(block)
  if not bytes then break end
  for b in string.gfind(bytes, ".") do
     io.write(string.format("%02X ", string.byte(b)))
  end
  io.write(string.rep("   ", block- string.len(bytes) + 1))
  io.write(string.gsub(bytes, "%c", "."), "
")
end

假设以 vip 来命名该程序脚本文件。能够使用例如以下命令来运行该程序处理其自身:

prompt> luavip vip
在 Unix  系统中它将会会产生一个例如以下的输出样式:

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
5D 2C 20 22 72 62 22 29 29 0A ], "rb")).
...
22 25 63 22 2C 20 22 2E 22 29 "%c", ".")
2C 20 22 5C 6E 22 29 0A 65 6E , "
").en
64 0A d.

20.3 关于文件的其他操作 

      函数 tmpfile 函数用来返回零时文件的句柄,而且其打开模式为 read/write模式。该零时文件在程序运行完后会自己主动进行清除。

函数 flush 用来应用针对文件的全部改动。同 write 函数一样。该函数的调用既能够按函数调用的方法使用 io.flush()来应用当前输出文 件;也能够按文件句柄方法的样式 f:flush()来应用文件 f。函数 seek 用来得到和设置一个文件的当前存取位置。

它的一般形式为filehandle:seek(whence,offset)。Whence 參数是一个表示偏移方式的字符串。它能够是 "set"。偏移值是从文件头開始;"cur"。偏移值从当前位置開始;"end",偏移值从文件尾往前计数。offset 即为偏移的数值,由 whence 的 值和 offset  相结合得到新的文件读取位置。该位置是实际从文件开头计数的字节数。whence 的默认值为 "cur"。offset 的默认值为 0。

这样调用 file:seek()得到的返回值就是文件当前的存取位置,且保持不变。file:seek("set")就是将文件的存取位置重设到文件开头。(返回值当然就是 0)。

而file:seek("end")就是将位置设为文件尾,同一时候就能够得到文件的大小。例如以下的代码实现了得到文件的大小而不改变存取位置。

function fsize (file)
    local current = file:seek()     -- get currentposition 
    local size = file:seek("end")                -- get filesize 
   file:seek("set", current)  -- restore position
   return size
end

以上的几个函数在出错时都将返回一个包括了错误信息的 nil 值。

原文地址:https://www.cnblogs.com/wgwyanfs/p/7294715.html