Ruby 基础教程第4版读书笔记 5 - IO

IO类

输入输出的种类

  1. 标准输入。获取从键盘输入的内容。
    • 通过预定义常量 STDIN 可调用操作标准输入的 IO 对象。
    • 全局变量 $stdin 也可以引用标准输入的 IO 对象。
    • 不指定接收者的 get 方法等都会默认从标准输入中获取数据。
  2. 标准输出。向标准输出写入的数据会显示在屏幕中。
    • 通过预定义常量 STDOUT 可调用操作标准输出的 IO 对象。
    • 另外,全局变量 $stdout 也可以引用标准输出的 IO 对象。
    • 不指定接收者的 puts, print, printf 方法会默认将数据写入标准输出。
  3. 标准错误输出。向标准错误输出写入的数据会显示在屏幕中。
    • 通过预定义常量 STDERR 可调用操作标准输出的 IO 对象。
    • 另外,全局变量 $stderr 也可以引用标准输出的 IO 对象。
    • 标准错误输出原本只用来输出错误和警告信息,但希望与程序正常输出的信息做出区别时也可以使用它。
  • 将输出结果重定向到文件时,标准输出的内容会被写入到文件,标准错误输出的内容被输出到屏幕中。
  • IO 对象是否与控制台关联,可以使用 tty? 方法判断。
# out.rb
$stdout.print "Output to $stdout. \n"
$stderr.print "Output to $stderr. \n"

# terminal
ruby out.rb > log.txt #=> Output to $stderr.

# log.txt
Output to $stdout.

文件输入、输出

通过 IO 类的子类 File 类可以进行文件的输入输出处理。

  • io = File.open(file, mode)
  • io = open(file, mode)
  • io.close
  • io.clise? 检查 IO 对象是否关闭了
  • File.read(file) 一次性读取文件的内容,在 windows 中不能使用这个方法读取图像等二进制数据。因为会对换行符进行转换,而无法得到正确结果。

mode 指定以何种方式打开文件,缺省为只读模式。windows 环境下,模式后加上 b 可表示二进制模式。

模式 作用
r 用只读模式打开文件
r+ 用读写模式打开文件
w 用只写模式打开文件。文件不存在则创建新文件;文件已存在则清空文件,既将文件大小设置为0
w+ 用读写模式打开文件,其余同 w
a 用追加模式打开文件。文件不存在则创建新的文件
a+ 用读取/追加模式打开文件。文件不存在则创建新的文件

一个程序中同时打开的文件的数量是有限制的,用完的文件应该尽快关闭。File.open 方法,如果使用块,则文件会在使用完毕后自动关闭。

输入操作

  • io.gets(rs)
  • io.each(rs)
  • io.each_line(rs)
  • io.readlines(rs) 一次性的读取所有数据,并返回每行数据作为元素封装的数组。

从 IO 类的对象 io 中读取一行数据。用参数 rs 的字符串分行,默认预定义变量 $/(默认值为 “\n”)。 这些方法返回的字符串包含末尾的换行符。用 chomp! 方法可以删除末尾换行符。 输入完毕后再尝试获取数据时,gets 方法会返回 nil。我们还可以用 eof? 方法检查输入是否已经完毕。

# 经典读取数据的用法
while line = io.gets
  line.chomp!
  #... do something
end

p io.eof?
io.each_line do |line|
  line.chomp!
  # ... do something
end
arr = io.readlines
arr.each_line do |line|
  line.chomp!
  # ... do something
end
  • io.lineno 使用 get,each_line 方法逐行读取数据时,会自动记录读取的行数。
  • io.lineno=(number) 改变行数。
  • io.each_char 逐个字符的读取 io 中的数据并执行块。
  • io.each_byte 逐个字节的读取 io 中的数据并执行块。得到的字符所对应的 ASCII 码以整数值的形式传递给块变量。
  • io.getc 只读取 io 中的一个字符。注意,根据编码的不同,一个字符可能对应不同的字节数。
  • io.ungetc(ch) 将参数 ch 指定的字符退回到 io 的输入缓存中。
  • io.getbyte 只读取 io 中的一个字节,返回的字节转换为 ASCII 码后的整数对象。
  • io.ungetbyte(byte) 将参数 byte 指定的一个字节退回到输入缓存中。参数为整数时,将该整数除以 256 后的余数作为 ASCII 码字符返回一个字节;参数作为字符串时,则返回字符串的第一个字节。
  • io.read(size) 读取参数 size 指定大小的数据。默认一次性读取全部数据并返回。
$stdin.each_line do |line|
  printf("%3d %s", $stdin.lineno, line)
end
while ch = io.getc
  #... do something
end
# hello.txt 的内容为 "Hello, Ruby.\n"
File.open("hello.txt") do |io|
  p io.read(5) #=> "Hello"
  p io.read #=> ", Ruby.\n"
end

输出操作

  • io.puts(str0, str1, ...)
  • io.putc(ch) 输出参数 ch 指定的字符编码所对应的字符。参数为字符串时输出首字符。
  • io.print(str0, str1, ...)
  • io.printf(fmt, arg0, arg1, ...) 按照指定的格式输出字符串。
  • io.write(str) 输出参数 str 指定的字符串。参数为 String 以外的参数时会自动将其转换为字符串。方法返回值为输出的字节数。
  • io<<str 输出参数 str 指定的字符串。返回接收者本身。
$stdout.puts "foo", "bar", "baz"

$stdout.putc(82)
$stdout.putc("Ruby")
$stdout.putc("\n")

size = $stdout.write("Hello. \n") #=> Hello.
p size #=> 6

文件指针

一般情况下会以行为单位处理文本数据,为了提高读取效率,可以将文件分为固定长度的文件块,来直接访问某个位置的数据。

我们用文件指针或者当前文件偏移量来表示 IO 对象指向的文件的位置。每当读写文件时,文件指针都会自动移动,而我们也可以使文件指针指向任意位置来读写数据。

  • io.pos
  • io.pos=(position)
  • io.seek(offset, whence) 移动文件指针的方法。offset 为用于指定位置的整数,参数 whence 用于指定 offset 如何移动。
  • io.rewind 将文件指针返回到文件开头。 lineno 方法返回的行编号为 0
  • io.truncate(size) 按照参数 size 指定的大小截断文件
#hello.txt 中的内容为 "Hello, Ruby.\n"

File.open("hello.txt") do |io|
  p io.read(5) #=> "Hello"
  p io.pos #=> 5
  io.pos = 0
  p io.gets #=> "Hello, Ruby.\n"
end

二进制模式与文本模式

  • 各个平台的换行符不一样,为了保证程序的兼容性,会将字符串中的”\n”转换为当前 OS 的换行符并输出。此外,在读取的时候也会将实际的换行符转换为 “\n”。
  • 当需要确定文件大小进行输入输出处理时,或者直接使用从其他平台复制文件时,如果进行换行符转换,就可能因为法问题。为了解决这个问题 Ruby 提供了不转换换行符的方法。
  • 换行符处理的前提是以行为单位做输入输出处理。需要转换时,称为文本模式,不需要转换时称作二进制模式。
  • 转换为二进制模式的 IO 对象无法再次转换为文本模式。
File.open("foo.txt", "w") do |file|
  io.binmode
  io.write "Hello, world.\n"
end

缓冲

  • 在使用 write,print 等方法操作 IO 对象时,程序内部会开辟出一定的空间来保存临时生成的数据副本。这部分空间称为缓冲。缓冲里积累一定的数据后,就会做实际的输出处理,然后清空缓冲。
  • 向控制台输出的两种方式中,标准错误输出完全不采用缓冲处理。因为,标准输出和标准错误输出混合使用时,程序实际输出的顺序可能会与程序代码中记录的顺序不一样。
  • 标准错误输出的主要目的时输出警告,错误等信息,因为执行结果必须马上反映出来。另外在显示程序中正常以外的信息时建议使用标准错误输出。
$stdout.print "out1"
$stderr.print "err1"
$stdout.print "out2"
$stdout.print "out3"
$stderr.print "err2"
$stdout.print "out4"

#=>
err1 err2
out1 out2 out3 out4
  • io.flush 强制输出缓冲中的数据。
  • io.sync
  • io.sync=(state) 程序写入缓冲时,flush 会自动调用。
$stdout.print "out1"; $stdout.flush
$stderr.print "err1"
$stdout.sync = true
$stdout.print "out1"
$stderr.print "err1"
$stdout.print "out2"
$stderr.print "err2"

#=> out1 err1 out2 err2

命令行交互

  • IO.popen(command, mode) 与其他命令进行数据处理。
  • open("|command", mode)
pattern = Regexp.new(ARGV[0])
filename = ARGV[1]
if /.gz$/ =~ filename
  file = IO.popen("gunzip -c #{filename}")
else
  file = File.open(filename)
end
file.each_line do |text|
  if pattern =~ text
    print text
  end
end
filename = ARGV[0]
open("|gunzip -c #{filename}") do |io
  io.each_line do |line|
    print line
  end
end

open-uri

  • 除了控制台和文件以外,进程间通信时使用的管道pipe,网络间通信时使用的套接字socket,都可以作为 IO 对象使用。
  • 使用 open-uri 库的功能时,不要使用 File.open 方法,只是用 open 方法即可。
require 'open-uri'

open("http://www.ruby-lang.org") do |io|
  puts io.read
end

url = "ftp://www.ruby-lang.org/pub/ruby/2.0/ruby-2.0.0-p0.tar.gz"
open(url) do |io|
  open("ruby-2.0.0-p0.tar.gz", "w") do |f|
    f.write(io.read)
  end
end

stringio

StringIO 就是用于模拟 IO 对象的对象。向其进行输出并不会输出到任何地方,而是会保存到对象中,之后可以用 read 方法来读取该输出。 另一种用法是,将字符串当做 IO 数据处理。将大数据保存在文件中,将小数据直接传输给别的处理,可以通过 StringIO 对象,这样程序就不用区分字符串和 IO 对象了。

require 'stringio'

io = StringIO.new
io.puts "A"
io.puts "B"
io.puts "C"
io.rewind
p io.read #=> "A\nB\nC\n"
require 'stringio'

io = StringIO.new("A\nB\nC\n")
p io.gets #=> "A\n"
p io.gets #=> "B\n"
p io.gets #=> "C\n"

如果觉得我的文章对您有用,请在支付宝公益平台找个项目捐点钱。 @Victor Dec 3, 2014

奉献爱心