在Ruby中分块读取文件

16

我需要以MB为单位分块读取文件,在Ruby中有更简洁的方法吗:

FILENAME="d:\\tmp\\file.bin"
MEGABYTE = 1024*1024
size = File.size(FILENAME)
open(FILENAME, "rb") do |io| 
  read = 0
  while read < size
    left = (size - read)
    cur = left < MEGABYTE ? left : MEGABYTE
    data = io.read(cur)
    read += data.size
    puts "READ #{cur} bytes" #yield data
  end
end
6个回答

22

改编自Ruby Cookbook第204页:

FILENAME = "d:\\tmp\\file.bin"
MEGABYTE = 1024 * 1024

class File
  def each_chunk(chunk_size = MEGABYTE)
    yield read(chunk_size) until eof?
  end
end

open(FILENAME, "rb") do |f|
  f.each_chunk { |chunk| puts chunk }
end

免责声明:我是一个 Ruby 新手,尚未测试过此内容。


是的,这个方法可行。不过我曾经认为如果要读取的字节数小于块大小,IO.read方法会抛出异常。我之前了解过IO.readbyte会抛出TruncatedDataError异常,所以就误以为read也一样。看来我的疏忽了。谢谢! - teleball

17

或者,如果你不想对 File 进行猴子补丁:

until my_file.eof?
  do_something_with( my_file.read( bytes ) )
end
例如,将上传的临时文件流式传输到新文件中:
# tempfile is a File instance
File.open( new_file, 'wb' ) do |f|
  # Read in small 65k chunks to limit memory usage
  f.write(tempfile.read(2**16)) until tempfile.eof?
end

快速问题:为什么是2**16?我看到其他人使用的是2**2010241024*1024。你有很多声望,所以想问问你。 :) - Joshua Pinter

5

您可以使用IO#each(sep, limit)方法,并将sep参数设置为nil或空字符串,例如:

chunk_size = 1024
File.open('/path/to/file.txt').each(nil, chunk_size) do |chunk|
  puts chunk
end

1
@EricDuminil 感谢您提醒我,我忘记了 sep 参数。现在应该可以分块读取文件。 - Weihang Jian
对我来说,eachsep==nilsep=='' 都返回了可变长度的块。 - Christian - Reinstate Monica C

0
如果您查看Ruby文档: http://ruby-doc.org/core-2.2.2/IO.html 有一行是这样的:
IO.foreach("testfile") {|x| print "GOT ", x }

唯一的注意事项是,由于此过程可以比生成的流更快地读取临时文件,所以我认为应该引入延迟。
IO.foreach("/tmp/streamfile") {|line|
  ParseLine.parse(line)
  sleep 0.3 #pause as this process will discontine if it doesn't allow some buffering 
}

0

https://ruby-doc.org/core-3.0.2/IO.html#method-i-read提供了一个使用read(length)迭代固定长度记录的示例:

# iterate over fixed length records
open("fixed-record-file") do |f|
  while record = f.read(256)
    # ...
  end
end

如果length是正整数,则read尝试读取length个字节而不进行任何转换(二进制模式)。如果在读取任何内容之前遇到EOF,则返回nil。如果在读取期间遇到EOF,则返回少于length个字节。对于整数length,生成的字符串始终处于ASCII-8BIT编码中。

-1
FILENAME="d:/tmp/file.bin"

class File
  MEGABYTE = 1024*1024

  def each_chunk(chunk_size=MEGABYTE)
    yield self.read(chunk_size) until self.eof?
  end
end

open(FILENAME, "rb") do |f|
  f.each_chunk {|chunk| puts chunk }
end

没问题,mbarkhau。我只是把常量定义移到了File类中,并为了清晰起见添加了一些"self"。


2
我不会使用额外的常量MEGABYTE,而是使用def each_chunk(chunk_size=2**20) - asaaki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接