计算文件中的行数而不将整个文件读入内存?
我正在处理大量的数据文件(每个数百万行)。
在开始处理之前,我想先计算一下文件中的行数,然后我可以指出处理的距离。
由于文件的大小,将整个文件读入内存是不实际的,只是为了统计有多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
split
和to_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#read
或IO#readlines
方法的上述任何答案,然后依次调用String方法来读取已读取的内容。 你说你不想把整个文件读入内存,而这正是它们所做的。 这就是为什么唐纳德·克努特(Donald Knuth)build议人们了解如何编程更接近硬件,因为如果他们不这样做,他们最终会写出“怪异的代码”。 显然你不想在不需要的时候接近硬件的代码,但这应该是常识。 但是,你应该学会认识到你必须更接近这个坚果和螺栓的情况。
不要试图获得比情况要求更多的“面向对象”。 对于想看起来比实际更复杂的新手来说,这是一个令人尴尬的陷阱。 当答案真的很简单的时候,你总是应该感到高兴的,当没有复杂性让你有机会写出令人印象深刻的代码的时候,不要失望。 但是,如果你想要看起来有点“面向对象”,并且不介意一次只读一整行(例如,你知道这些行很短),你可以这样做
f = File.new("/path/to/whatever") num_newlines = 0 f.each_line do num_newlines += 1 end
这将是一个很好的折衷,但只有在线不太长的情况下才能比我的第一个解决scheme更快。
使用foreach
而不inject
比inject
快3%。 两者都比使用getc
更快(比我的经验多100倍)。
使用foreach
而不inject
也可以稍微简化(相对于此线程中其他地方给出的片段),如下所示:
count = 0; File.foreach(path) { count+=1} puts "count: #{count}"