看起来问题可能出在这一行:
File.open(file, "rb") { |f| tf.write f.read }
您正在通过执行“啜饮”您的输入文件f.read
。slurping 意味着整个文件被读入内存,这根本不可扩展,并且是使用read
没有长度的结果。
相反,我会做一些事情来读取和写入块中的文件,这样你就有了一致的内存使用。这读取 1MB 块。您可以根据自己的需要进行调整:
BLOCKSIZE_TO_READ = 1024 * 1000
File.open(file, "rb") do |fi|
while buffer = fi.read(BLOCKSIZE_TO_READ)
tf.write buffer
end
end
以下是文档所说的read
:
如果长度是一个正整数,它会尝试读取长度字节而不进行任何转换(二进制模式)。它返回 nil 或长度为 1 到 length 个字节的字符串。nil 表示它在开始时遇到了 EOF。1 到长度为 1 字节的字符串意味着它在读取结果后遇到了 EOF。长度字节字符串意味着它不符合 EOF。结果字符串始终为 ASCII-8BIT 编码。
另一个问题是您似乎没有正确打开输出文件:
tarfile = File.open("#{Pathname.new(path).realpath.to_s}.tar","w")
您正在以“文本”模式编写它,因为"w"
. 相反,您需要以二进制模式编写"wb"
,因为 tarball 包含二进制(压缩)数据:
tarfile = File.open("#{Pathname.new(path).realpath.to_s}.tar","wb")
将原始代码重写为更像我想看到的那样,结果是:
BLOCKSIZE_TO_READ = 1024 * 1000
def create_tarball(path)
tar_filename = Pathname.new(path).realpath.to_path + '.tar'
File.open(tar_filename, 'wb') do |tarfile|
Gem::Package::TarWriter.new(tarfile) do |tar|
Dir[File.join(path, '**/*')].each do |file|
mode = File.stat(file).mode
relative_file = file.sub(/^#{ Regexp.escape(path) }\/?/, '')
if File.directory?(file)
tar.mkdir(relative_file, mode)
else
tar.add_file(relative_file, mode) do |tf|
File.open(file, 'rb') do |f|
while buffer = f.read(BLOCKSIZE_TO_READ)
tf.write buffer
end
end
end
end
end
end
end
tar_filename
end
BLOCKSIZE_TO_READ
应该在文件的顶部,因为它是一个常量并且是“可调整的”——比代码主体更可能被更改。
该方法返回 tarball 的路径,而不是像原始代码那样的 IO 句柄。使用IO.open
自动关闭输出的块形式,这将导致任何后续open
自动rewind
。我更喜欢传递路径字符串而不是文件的 IO 句柄。
我还将一些方法参数包裹在括号中。虽然 Ruby 中的方法参数周围不需要括号,而且有些人避开了括号,但我认为它们通过分隔参数的开始和结束位置使代码更易于维护。当您将参数和块传递给方法时,它们还避免混淆 Ruby——这是众所周知的错误原因。