处理这个大文本文件最有效的方法是什么?

时间:2012-09-13 18:23:56

标签: ruby

当我将一个文本文件读入内存时,由于新的行,它会在末尾带有'\ n'文本。

["Hello\n", "my\n", "name\n", "is\n", "John\n"] 

以下是我阅读文本文件的方式

array = File.readlines('text_file.txt')

我需要对这个文本数组进行大量的处理,所以我想知道在我第一次创建数组时是否应该删除“\ n”,或者当我使用regex对每个元素进行处理时,性能明智的。

我写了一些(不可否​​认的)测试代码来删除“\ n”

array = []
File.open('text_file.txt', "r").each_line do |line|
    data = line.split(/\n/)
    array << data
end
array.flatten!

如果我在第一次创建数组时删除“\ n”,有没有更好的方法呢?

如果我想将文件读入Set而不是(为了性能),是否有类似于readlines的方法呢?

5 个答案:

答案 0 :(得分:4)

您需要运行基准测试,使用Ruby的内置Benchmark来确定您最快的选择。

然而,根据经验,我发现“啜饮”文件,即一次性读取,并不比使用IO.foreach or File.foreach的循环快。这是因为Ruby和底层操作系统会在读取时进行文件缓冲,从而允许循环从内存发生,而不是直接从磁盘发生。 foreach不会为split删除行终结符,因此如果您想改变行读取,则需要添加chompchomp!在:

File.foreach('/path/to/file') do |li|
  puts li.chomp
end

File.foreach('/path/to/file') do |li|
  li.chomp!
  puts li
end

此外,啜食还存在不可扩展的问题;您最终可能会尝试读取比内存更大的文件,让您的机器瘫痪,而逐行阅读将永远不会这样做。


以下是一些表现数字:

#!/usr/bin/env ruby

require 'benchmark'
require 'fileutils'

FILENAME = 'test.txt'
LOOPS = 1

puts "Ruby Version: #{RUBY_VERSION}"
puts "Filesize being read: #{File.size(FILENAME)}"
puts "Lines in file: #{`wc -l #{FILENAME}`.split.first}"

Benchmark.bm(20) do |x|
  x.report('read.split')           { LOOPS.times { File.read(FILENAME).split("\n") }}
  x.report('read.lines.chomp')     { LOOPS.times { File.read(FILENAME).lines.map(&:chomp) }}
  x.report('readlines.map.chomp1') { LOOPS.times { File.readlines(FILENAME).map(&:chomp) }}
  x.report('readlines.map.chomp2') { LOOPS.times { File.readlines(FILENAME).map{ |s| s.chomp } }}
  x.report('foreach.map.chomp1')   { LOOPS.times { File.foreach(FILENAME).map(&:chomp) }}
  x.report('foreach.map.chomp2')   { LOOPS.times { File.foreach(FILENAME).map{ |s| s.chomp } }}
end

结果:

Ruby Version: 1.9.3
Filesize being read: 42026131
Lines in file: 465440
                           user     system      total        real
read.split             0.150000   0.060000   0.210000 (  0.213365)
read.lines.chomp       0.470000   0.070000   0.540000 (  0.541266)
readlines.map.chomp1   0.450000   0.090000   0.540000 (  0.535465)
readlines.map.chomp2   0.550000   0.060000   0.610000 (  0.616674)
foreach.map.chomp1     0.580000   0.060000   0.640000 (  0.641563)
foreach.map.chomp2     0.620000   0.050000   0.670000 (  0.662912)

在今天的机器上,一个42MB的文件可以非常安全地读入RAM。我看到的文件比那些不适合我们某些生产主机内存的文件大得多。虽然foreach较慢,但如果没有足够的内存,它也不会通过吸取所有内存来使机器瘫痪。

在Ruby 1.9.3上,使用map(&:chomp)方法而不是旧形式的map { |s| s.chomp },速度要快得多。对于旧版本的Ruby来说,情况并非如此,因此需要注意。

另外,请注意,在我几年前的Mac Pro上,上述所有内容都在不到一秒的时间内处理了数据。总而言之,我要说担心加载速度是过早的优化,而真正的问题将是加载数据后所做的事情。

答案 1 :(得分:4)

我使用String#chomp

lines = open('text_file.txt').lines.map(&:chomp)

答案 2 :(得分:0)

我希望在这些情况下使用strip over split,并且在第一次处理该行之后立即执行。在readline之后使用split是overkill imo。所以代码片段将是

array = []
File.open('text_file.txt', "r").each_line do |line|
    array << data.strip
end

答案 3 :(得分:0)

如果您想摆脱结束换行符,可以String#chompString#rstrip。我首选的方法是chomp。

因此,您可以轻松地执行以下操作:

lines.map! { |line| line.chomp }
# or
lines.map! { |line| line.rstrip }

答案 4 :(得分:0)

mvelez@argo:~$ cat test.txt
Hello
my
name
is
John

一个班轮:

arr = File.open("test.txt",'r').read.split

irb

中分解此内容
irb(main):002:0> f = File.open("test.txt",'r')
=> #<File:test.txt>
irb(main):003:0> file_contents = f.read
=> "Hello\nmy\nname\nis\nJohn\n\n"
irb(main):004:0> file_contents.split
=> ["Hello", "my", "name", "is", "John"]