我正在处理大量数据文件(每个数百万行)。
在开始处理之前,我想得到文件中行数的计数,然后我可以指出处理的距离。
由于文件的大小,将整个文件读入内存是不切实际的,只计算有多少行。有没有人对如何做到这一点有很好的建议?
答案 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
split
和to_i
将其添加到数字中。
答案 6 :(得分:3)
免责声明::现有的基准测试使用count
而不是length
或size
,并且
读恕我直言很乏味。因此,这个新答案。
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#read
或IO#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)
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