计算文件中的行数而不将整个文件读入内存?

时间:2010-04-16 04:02:31

标签: ruby

我正在处理大量数据文件(每个数百万行)。

在开始处理之前,我想得到文件中行数的计数,然后我可以指出处理的距离。

由于文件的大小,将整个文件读入内存是不切实际的,只计算有多少行。有没有人对如何做到这一点有很好的建议?

15 个答案:

答案 0 :(得分:72)

一次读取一行文件:

count = File.foreach(filename).inject(0) {|c, line| c+1}

或Perl-ish

File.foreach(filename) {}
count = $.

count = 0
File.open(filename) {|f| count = f.read.count("\n")}

会慢于

count = %x{wc -l #{filename}}.split.first.to_i

答案 1 :(得分:64)

如果您在Unix环境中,可以让wc -l完成工作。

它不会将整个文件加载到内存中;因为它针对流文件和计数字/行进行了优化,所以性能足够好,而不是自己在Ruby中流式传输文件。

<强> SSCCE:

filename = 'a_file/somewhere.txt'
line_count = `wc -l "#{filename}"`.strip.split(' ')[0].to_i
p line_count

或者,如果您想要在命令行上传递文件集合:

wc_output = `wc -l "#{ARGV.join('" "')}"`
line_count = wc_output.match(/^ *([0-9]+) +total$/).captures[0].to_i
p line_count

答案 2 :(得分:13)

使用哪种语言无关紧要,如果行长度可变,则必须读取整个文件。那是因为新行可能在任何地方,如果没有读取文件就无法知道(假设它没有被缓存,一般来说不是这样)。

如果您想表明进度,您有两个现实的选择。您可以根据假定的行长度推断进度:

assumed lines in file = size of file / assumed line size
progress = lines processed / assumed lines in file * 100%

因为您知道文件的大小。或者,您可以测量进度:

progress = bytes processed / size of file * 100%

这应该足够了。

答案 3 :(得分:11)

使用ruby:

file=File.open("path-to-file","r")
file.readlines.size

比325.477行文件上的wc -l快39毫秒

答案 4 :(得分:9)

发布的解决方案摘要

require 'benchmark'
require 'csv'

filename = "name.csv"

Benchmark.bm do |x|
  x.report { `wc -l < #{filename}`.to_i }
  x.report { File.open(filename).inject(0) { |c, line| c + 1 } }
  x.report { File.foreach(filename).inject(0) {|c, line| c+1} }
  x.report { File.read(filename).scan(/\n/).count }
  x.report { CSV.open(filename, "r").readlines.count }
end

带有807802行的文件:

       user     system      total        real
   0.000000   0.000000   0.010000 (  0.030606)
   0.370000   0.050000   0.420000 (  0.412472)
   0.360000   0.010000   0.370000 (  0.374642)
   0.290000   0.020000   0.310000 (  0.315488)
   3.190000   0.060000   3.250000 (  3.245171)

答案 5 :(得分:3)

与DJ的答案相同,但提供实际的Ruby代码:

count = %x{wc -l file_path}.split[0].to_i

第一部分

wc -l file_path

给你

num_lines file_path

splitto_i将其添加到数字中。

答案 6 :(得分:3)

免责声明::现有的基准测试使用count而不是lengthsize,并且 读恕我直言很乏味。因此,这个新答案。

基准

require "benchmark"
require "benchmark/ips"
require "csv"

filename = ENV.fetch("FILENAME")

Benchmark.ips do |x|
  x.report("wc") { `wc -l #{filename}`.to_i }
  x.report("open") { File.open(filename).inject(0, :next) }
  x.report("foreach") { File.foreach(filename).inject(0, :next) }
  x.report("foreach $.") { File.foreach(filename) {}; $. }
  x.report("read.scan.length") { File.read(filename).scan(/\n/).length }
  x.report("CSV.open.readlines") { CSV.open(filename, "r").readlines.length }
  x.report("IO.readlines.length") { IO.readlines(filename).length }

  x.compare!
end

在配备2.3 GHz Intel Core i5处理器的MacBook Pro(2017)上:

Warming up --------------------------------------
                  wc     8.000  i/100ms
                open     2.000  i/100ms
             foreach     2.000  i/100ms
          foreach $.     2.000  i/100ms
    read.scan.length     2.000  i/100ms
  CSV.open.readlines     1.000  i/100ms
 IO.readlines.length     2.000  i/100ms
Calculating -------------------------------------
                  wc    115.014  (±21.7%) i/s -    552.000  in   5.020531s
                open     22.450  (±26.7%) i/s -    104.000  in   5.049692s
             foreach     32.669  (±27.5%) i/s -    150.000  in   5.046793s
          foreach $.     25.244  (±31.7%) i/s -    112.000  in   5.020499s
    read.scan.length     44.102  (±31.7%) i/s -    190.000  in   5.033218s
  CSV.open.readlines      2.395  (±41.8%) i/s -     12.000  in   5.262561s
 IO.readlines.length     36.567  (±27.3%) i/s -    162.000  in   5.089395s

Comparison:
                  wc:      115.0 i/s
    read.scan.length:       44.1 i/s - 2.61x  slower
 IO.readlines.length:       36.6 i/s - 3.15x  slower
             foreach:       32.7 i/s - 3.52x  slower
          foreach $.:       25.2 i/s - 4.56x  slower
                open:       22.4 i/s - 5.12x  slower
  CSV.open.readlines:        2.4 i/s - 48.02x  slower

这是由包含75 516行和3 532 510个字符(每行约47个字符)的文件完成的。您应该使用自己的文件/尺寸和计算机尝试此操作,以获得精确的结果。

答案 7 :(得分:2)

由于我不完全理解的原因,使用File扫描文件以获取换行符似乎比执行CSV#readlines.count要快得多。

以下基准使用的CSV文件包含1,045,574行数据和4列:

       user     system      total        real
   0.639000   0.047000   0.686000 (  0.682000)
  17.067000   0.171000  17.238000 ( 17.221173)

基准的代码如下:

require 'benchmark'
require 'csv'

file = "1-25-2013 DATA.csv"

Benchmark.bm do |x|
    x.report { File.read(file).scan(/\n/).count }
    x.report { CSV.open(file, "r").readlines.count }
end

如您所见,扫描文件以获取新行的速度要快一个数量级。

答案 8 :(得分:2)

我有这个班轮。

puts File.foreach('myfile.txt').count

答案 9 :(得分:1)

如果文件是CSV文件,如果文件的内容是数字,则记录的长度应该非常均匀。将文件的大小除以记录的长度或前100条记录的平均值是不合理的。

答案 10 :(得分:1)

超过135k行的测试结果如下所示。 这是我的基准代码。

x_connectionEstablished

结果是

 file_name = '100m.csv'
 Benchmark.bm do |x|
   x.report { File.new(file_name).readlines.size }
   x.report { `wc -l "#{file_name}"`.strip.split(' ')[0].to_i }
   x.report { File.read(file_name).scan(/\n/).count }
 end

user system total real 0.100000 0.040000 0.140000 ( 0.143636) 0.000000 0.000000 0.090000 ( 0.093293) 0.380000 0.060000 0.440000 ( 0.464925) 代码有一个问题。 如果文件中只有一行,而最后一个字符不以wc -l结尾,则count为零。

因此,我推荐在计算多于一行时调用wc。

答案 11 :(得分:0)

使用UNIX样式的文本文件,它非常简单

f = File.new("/path/to/whatever")
num_newlines = 0
while (c = f.getc) != nil
  num_newlines += 1 if c == "\n"
end

就是这样。对于MS Windows文本文件,您必须检查 一系列的“\ r \ n”而不仅仅是“\ n”,但这并不多 更加困难。对于Mac OS Classic文本文件(而不是 Mac OS X),你会检查“\ r”而不是“\ n”。

所以,是的,这看起来像C.那么什么? C很棒,Ruby也是 太棒了,因为当C答案最简单的时候就是你能做到的 期待你的Ruby代码看起来像。希望你的妄想没有 已经被Java打败了。

顺便说一下,请不要考虑上面的任何答案 使用IO#readIO#readlines方法依次调用a 读取内容的字符串方法。你说你不想 将整个文件读入内存,这正是它们的作用。 这就是Donald Knuth建议人们了解如何编程的原因 更靠近硬件,因为如果他们不这样做,他们最终会写作 “奇怪的代码”。显然你不想接近代码 硬件,只要你不必,但这应该是常识。 但是,您应该学会识别您拥有的实例 接近像这样的螺母和螺栓。

并且不要试图获得比面向对象更多的“面向对象” 要求。对于想要看的新手来说,这是一个令人尴尬的陷阱 比实际更复杂。你应该永远高兴 对于答案真的很简单的时代,而不是 当没有复杂性给你机会时,我很失望 写“令人印象深刻”的代码。但是如果你想看一下 “面向对象”并不介意阅读整行 一次记忆(也就是你知道线条足够短),你 可以做到这一点

f = File.new("/path/to/whatever")
num_newlines = 0
f.each_line do
  num_newlines += 1
end

这将是一个很好的折衷方案,但前提条件不是这样 在这种情况下,它甚至可能比我的第一次运行得更快 溶液

答案 12 :(得分:0)

使用不foreach的{​​{1}}比使用inject快3%。与使用inject相比,两者都快得多(我的经验超过100倍)。

使用不带getc的{​​{1}}也可以略微简化(相对于此线程中其他地方给出的代码段),如下所示:

foreach

答案 13 :(得分:0)

Ruby中的

wc -l内存较少,懒惰的方式:

(ARGV.length == 0 ?
 [["", STDIN]] :
    ARGV.lazy.map { |file_name|
        [file_name, File.open(file_name)]
})
.map { |file_name, file|
    "%8d %s\n" % [*file
                    .each_line
                    .lazy
                    .map { |line| 1 }
                    .reduce(:+), file_name]
}
.each(&:display)

最初由Shugo Maeda显示。

示例:

$ curl -s -o wc.rb -L https://git.io/vVrQi
$ chmod u+x wc.rb
$ ./wc.rb huge_data_file.csv
  43217291 huge_data_file.csv

答案 14 :(得分:-2)

您只能阅读最后一行并查看其编号:

f = File.new('huge-file')
f.readlines[-1]
count = f.lineno