Подсчитать количество строк в файле, не считывая весь файл в память? - PullRequest
51 голосов
/ 16 апреля 2010

Я обрабатываю огромные файлы данных (миллионы строк каждый).

Перед тем, как приступить к обработке, я бы хотел подсчитать количество строк в файле, чтобы потом можно было указать, как далеко продвигается обработка.

Из-за размера файлов нецелесообразно считывать весь файл в память, а просто считать, сколько строк. У кого-нибудь есть хорошее предложение, как это сделать?

Ответы [ 14 ]

72 голосов
/ 16 апреля 2010

Чтение файла строкой за раз:

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
65 голосов
/ 16 апреля 2010

Если вы находитесь в среде 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
14 голосов
/ 16 апреля 2010

Неважно, какой язык вы используете, вам придется читать весь файл, если строки имеют переменную длину. Это потому, что переводы строк могут быть где угодно, и нет никакого способа узнать об этом, не прочитав файл (при условии, что он не кэширован, что, вообще говоря, нет).

Если вы хотите указать прогресс, у вас есть два реалистичных варианта. Вы можете экстраполировать прогресс на основе предполагаемой длины строки:

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%

Этого должно быть достаточно.

11 голосов
/ 26 марта 2014

с использованием ruby:

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

39 миллисекунд быстрее, чем wc -l для файла с строками 325.477

9 голосов
/ 14 июля 2017

Сводка опубликованных решений

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)
3 голосов
/ 11 августа 2013

То же, что и ответ диджея, но с указанием действительного кода Ruby:

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

Первая часть

wc -l file_path

Дает вам

num_lines file_path

split и to_i помещают это в число.

2 голосов
/ 17 января 2015

У меня есть этот лайнер.

puts File.foreach('myfile.txt').count
2 голосов
/ 07 марта 2013

По причинам, которые я не до конца понимаю, сканирование файла на наличие новых строк с использованием 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

Как видите, сканирование файла на наличие новых строк происходит на порядок быстрее.

1 голос
/ 11 января 2017

Результаты теста для более чем 135 тыс. Строк приведены ниже. Это мой контрольный код.

 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, если вы считаете более одной строки.

1 голос
/ 30 марта 2013

Если файл представляет собой файл CSV, длина записей должна быть довольно равномерной, если содержимое файла является числовым. Разве не имеет смысла просто делить размер файла на длину записи или среднее значение первых 100 записей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...