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

smn*_*ven 53 ruby

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

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

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

gle*_*man 74

一次读取一行文件:

count = File.foreach(filename).inject(0) {|c, line| c+1}
Run Code Online (Sandbox Code Playgroud)

或Perl-ish

File.foreach(filename) {}
count = $.
Run Code Online (Sandbox Code Playgroud)

要么

count = 0
File.open(filename) {|f| count = f.read.count("\n")}
Run Code Online (Sandbox Code Playgroud)

会慢一些

count = %x{wc -l #{filename}}.split.first.to_i
Run Code Online (Sandbox Code Playgroud)

  • 最后一个是最干净的,因为我们可以假设"wc"针对良好的I/O速度进行了优化.".split.first"是多余的,不要忘记在文件名周围添加单引号,否则它将在有空格的文件名上失败.简化:%x {wc -l'#{filename}'}.to_i (5认同)
  • 或`count =%x {wc -l <​​"#{filename}"}.to_i` (4认同)
  • @deafgreatdane我不认为`wc`是"干净的".现在它不会在Windows上运行..我会在性能上受到轻微影响(在相同的复杂性类中)以避免可移植性问题. (4认同)

DJ.*_*DJ. 67

如果你在Unix环境中,你可以放手去做wc -l.

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

SSCCE:

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

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

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

  • 有一个边缘条件:如果文件的最后一行没有换行符,则wc会出现一个短路.这是通过posix设计,见http://backreference.org/2010/05/23/sanitizing-files-with-no-trailing-newline/ (8认同)
  • 请做的不仅仅是引用方法,在实践中引用wc -l的示例.不是每个人都知道对你来说显而易见的事情.(我知道,"Google更难!"......但如果我们都能像Ruby一样,我们会本能地这样做.) (5认同)
  • WC非常快,人们可能不需要进度计数器. (3认同)
  • 边缘条件的解决方案(如果最后一行没有换行符):`count =%x {sed -n'='#{file} | 厕所-l} .to_i`参考:http://stackoverflow.com/questions/12616039/wc-command-of-mac-showing-one-less-result (3认同)
  • 格伦的回答是正确的.大家都感谢格伦. (2认同)

cle*_*tus 15

无论你使用什么语言都没关系,如果行长度可变,你将不得不阅读整个文件.那是因为新行可能在任何地方,如果没有读取文件就没有办法知道(假设它没有被缓存,一般来说它不是).

如果要指示进度,则有两个实际选项.您可以根据假定的行长度推断进度:

assumed lines in file = size of file / assumed line size
progress = lines processed / assumed lines in file * 100%
Run Code Online (Sandbox Code Playgroud)

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

progress = bytes processed / size of file * 100%
Run Code Online (Sandbox Code Playgroud)

这应该足够了.

  • 我假设原始海报可以通读文件,只是没有将其全部内容保存在内存中。 (2认同)

JBo*_*Boy 11

使用红宝石:

file=File.open("path-to-file","r")
file.readlines.size
Run Code Online (Sandbox Code Playgroud)

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

  • 您的系统的“wc”有问题。 (3认同)

Exs*_*emt 10

已发布解决方案的摘要

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
Run Code Online (Sandbox Code Playgroud)

文件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)
Run Code Online (Sandbox Code Playgroud)


Uly*_* BN 8

免责声明:使用已经存在的基准测试count而不是lengthor size(在 ruby​​ 中这是出了名的慢)。恕我直言,阅读起来很乏味。因此有了这个新答案。

\n

基准

\n
require "benchmark"\nrequire "benchmark/ips"\nrequire "csv"\n\nfilename = ENV.fetch("FILENAME")\n\nBenchmark.ips do |x|\n  x.report("wc") { `wc -l #{filename}`.to_i }\n  x.report("open") { File.open(filename).inject(0, :next) }\n  x.report("foreach") { File.foreach(filename).inject(0, :next) }\n  x.report("foreach $.") { File.foreach(filename) {}; $. }\n  x.report("read.scan.length") { File.read(filename).scan(/\\n/).length }\n  x.report("CSV.open.readlines") { CSV.open(filename, "r").readlines.length }\n  x.report("IO.readlines.length") { IO.readlines(filename).length }\n\n  x.compare!\nend\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
Warming up --------------------------------------\n                  wc     8.000  i/100ms\n                open     2.000  i/100ms\n             foreach     2.000  i/100ms\n          foreach $.     2.000  i/100ms\n    read.scan.length     2.000  i/100ms\n  CSV.open.readlines     1.000  i/100ms\n IO.readlines.length     2.000  i/100ms\nCalculating -------------------------------------\n                  wc    115.014  (\xc2\xb121.7%) i/s -    552.000  in   5.020531s\n                open     22.450  (\xc2\xb126.7%) i/s -    104.000  in   5.049692s\n             foreach     32.669  (\xc2\xb127.5%) i/s -    150.000  in   5.046793s\n          foreach $.     25.244  (\xc2\xb131.7%) i/s -    112.000  in   5.020499s\n    read.scan.length     44.102  (\xc2\xb131.7%) i/s -    190.000  in   5.033218s\n  CSV.open.readlines      2.395  (\xc2\xb141.8%) i/s -     12.000  in   5.262561s\n IO.readlines.length     36.567  (\xc2\xb127.3%) i/s -    162.000  in   5.089395s\n\nComparison:\n                  wc:      115.0 i/s\n    read.scan.length:       44.1 i/s - 2.61x  slower\n IO.readlines.length:       36.6 i/s - 3.15x  slower\n             foreach:       32.7 i/s - 3.52x  slower\n          foreach $.:       25.2 i/s - 4.56x  slower\n                open:       22.4 i/s - 5.12x  slower\n  CSV.open.readlines:        2.4 i/s - 48.02x  slower\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n