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

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

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

由于文件的大小,将整个文件读入内存是不实际的,只是为了统计有多less行。 有没有人有一个好的build议,如何做到这一点?

如果你在Unix环境下,你可以让wc -l做这个工作。

它不会将整个文件加载到内存中; 因为它是针对stream式文件进行优化的,并且对字符/行数进行了优化,所以性能足够好,而不必在Ruby中自行stream式传输文件

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 

一次读取一行文件:

 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 

使用什么语言并不重要,如果行长度可变,您将不得不读取整个文件。 这是因为换行符可能在任何地方,没有阅读文件就无法知道(假设它没有被caching,一般来说不是这样)。

如果你想表明进展,你有两个现实的select。 您可以根据假设的行长来推断进度:

 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% 

这应该是足够的。

使用ruby:

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

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

发布的解决scheme摘要

 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) 

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

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

  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 

正如你所看到的,扫描文件换行符的速度要快一个数量级。

与DJ的答案相同,但是给出了实际的Ruby代码:

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

第一部分

 wc -l file_path 

给你

 num_lines file_path 

splitto_i把它放入一个数字。

我有这个class轮

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

在Ruby中wc -l用较less的内存,懒散的方式:

 (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 

如果该文件是一个CSV文件,如果文件的内容是数字,则logging的长度应该是相当一致的。 把文件的大小除以logging长度或前100条logging的平均值是否合理?

下面显示了超过135k行的testing结果。 这是我的基准代码。

  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代码有一个问题。 如果文件中只有一行,并且最后一个字符不以\n结尾,则count为零。

所以,我build议你打电话给wc,当你多计一行。

使用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代码看起来像。 希望你的dain还没有被Java压制过。

顺便说一下,请不要考虑使用IO#readIO#readlines方法的上述任何答案,然后依次调用String方法来读取已读取的内容。 你说你不想把整个文件读入内存,而这正是它们所做的。 这就是为什么唐纳德·克努特(Donald Knuth)build议人们了解如何编程更接近硬件,因为如果他们不这样做,他们最终会写出“怪异的代码”。 显然你不想在不需要的时候接近硬件的代码,但这应该是常识。 但是,你应该学会认识到你必须更接近这个坚果和螺栓的情况。

不要试图获得比情况要求更多的“面向对象”。 对于想看起来比实际更复杂的新手来说,这是一个令人尴尬的陷阱。 当答案真的很简单的时候,你总是应该感到高兴的,当没有复杂性让你有机会写出令人印象深刻的代码的时候,不要失望。 但是,如果你想要看起来有点“面向对象”,并且不介意一次只读一整行(例如,你知道这些行很短),你可以这样做

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

这将是一个很好的折衷,但只有在线不太长的情况下才能比我的第一个解决scheme更快。

使用foreach而不injectinject快3%。 两者都比使用getc更快(比我的经验多100倍)。

使用foreach而不inject也可以稍微简化(相对于此线程中其他地方给出的片段),如下所示:

 count = 0; File.foreach(path) { count+=1} puts "count: #{count}"