代码之家  ›  专栏  ›  技术社区  ›  smnirven

在不将整个文件读取到内存的情况下,计算文件中的行数?

  •  48
  • smnirven  · 技术社区  · 14 年前

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

    在我开始处理之前,我想得到文件中的行数,这样我就可以指出处理过程的持续时间。

    由于文件的大小,将整个文件读取到内存中是不实际的,只需要计算有多少行。有人对怎么做有好的建议吗?

    14 回复  |  直到 14 年前
        1
  •  64
  •   Jason    9 年前

    如果您在一个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
  •  72
  •   glenn jackman    13 年前

    一次读取一行文件:

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

    或perl

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

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

    会比

    count = %x{wc -l #{filename}}.split.first.to_i
    
        3
  •  13
  •   cletus    14 年前

    不管使用什么语言,如果行的长度可变,就必须读取整个文件。这是因为换行符可以在任何地方,如果不读取文件(假设文件没有缓存,一般来说不是缓存文件),就无法知道。

    如果你想指出进展,你有两个现实的选择。可以根据假定的线条长度推断进度:

    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%
    

    这就足够了。

        4
  •  11
  •   JBoy    10 年前

    使用红宝石:

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

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

        5
  •  7
  •   Exsemt    7 年前

    已发布解决方案的摘要

    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)
    
        6
  •  3
  •   Yu Hao    11 年前

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

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

    第一部分

    wc -l file_path
    

    给你

    num_lines file_path
    

    这个 split to_i 把它记进一个数字。

        7
  •  2
  •   fbonetti    11 年前

    出于我不完全理解的原因,扫描文件以查找换行符 File 似乎比做的快很多 CSV#readlines.count .

    以下基准使用了一个包含1045574行数据和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
    

    如您所见,扫描文件换行速度快了一个数量级。

        8
  •  2
  •   vikas027    10 年前

    我有一个衬里。

    puts File.foreach('myfile.txt').count
    
        9
  •  1
  •   ptcesq    11 年前

    如果文件是一个csv文件,那么如果文件的内容是数字,那么记录的长度应该相当一致。将文件的大小除以记录的长度或前100条记录的平均值是否有意义?

        10
  •  1
  •   altamic    8 年前

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

    (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
    
        11
  •  1
  •   SeongSu    8 年前

    超过135K条线路的测试结果如下所示。 这是我的基准代码。

     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 ,则计数为零。

    所以,我建议当你数到多于一行的时候打电话给wc。

        12
  •  0
  •   the Tin Man    11 年前

    对于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经典文本文件(与 Mac OS X),您将检查“r”而不是“n”。

    所以,是的,这看起来像C。那又怎样?C棒极了,Ruby棒极了 太棒了,因为当C答案最简单的时候,这就是你能做到的。 希望您的Ruby代码看起来像。希望你的Dain没有 已经被爪哇肆虐了。

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

    不要试图得到更多的“面向对象”的情况 呼吁。这是一个令人尴尬的陷阱,为那些想看 比实际情况更复杂。你应该永远高兴 当答案真的很简单,而不是 当没有复杂的事情给你机会时,你会很失望 写“令人印象深刻的”代码。但是如果你想看起来 “面向对象”并且不介意将整行内容 一次记忆(也就是说,你知道线路足够短),你 可以做到这一点

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

    这将是一个很好的折中方案,但前提是线路不太合适。 在这种情况下,它可能比我的第一个运行得更快。 解决方案。

        13
  •  0
  •   peak    8 年前

    使用 foreach 没有 inject 比用的快3% 注入 . 两者都比使用 getc .

    使用 前额 没有 注入 也可以稍微简化(相对于这个线程中其他地方给出的代码片段),如下所示:

    count = 0;  File.foreach(path) { count+=1}
    puts "count: #{count}"
    
        14
  •  -2
  •   D.S.    6 年前

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

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