Подтвердить что ты не робот

Подсчитайте количество строк в файле, не прочитав весь файл в памяти?

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

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

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

4b9b3361

Ответ 1

Если вы находитесь в среде 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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 5

Резюме опубликованных решений

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

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

Как вы можете видеть, сканирование файла для строк новой строки на порядок выше.

Ответ 7

То же, что и DJ, но дающий реальный код Ruby:

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

Первая часть

wc -l file_path

Дает вам

num_lines file_path

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

Ответ 8

У меня есть этот один вкладыш.

puts File.foreach('myfile.txt').count

Ответ 9

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

Ответ 10

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

Ответ 11

Результаты тестирования для линий более 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, когда вы подсчитываете более одной строки.

Ответ 12

С текстовыми файлами в стиле 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, в свою очередь, вызывающий Строковый метод на том, что было прочитано. Вы сказали, что не хотите прочитайте весь файл в памяти и что именно они делают. Вот почему Дональд Кнут рекомендует людям понять, как программировать ближе к оборудованию, потому что, если они этого не сделают, они "странный код". Очевидно, что вы не хотите кодировать когда вам это не нужно, но это должно быть здравым смыслом. Однако вы должны научиться распознавать случаи, которые у вас есть чтобы приблизиться к гайкам и болтам, таким как этот.

И не пытайтесь получить больше "объектно-ориентированных", чем ситуация призывает. Это неловкая ловушка для новичков, которые хотят смотреть более сложные, чем они есть на самом деле. Вы всегда должны быть рады для тех случаев, когда ответ действительно прост, а не разочарованы, когда нет сложности, чтобы дать вам возможность написать "впечатляющий" код. Однако, если вы хотите выглядеть несколько "объектно-ориентированный" и не против читать всю строку в (т.е. вы знаете, что линии достаточно короткие), вы может сделать это

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

Это был бы хороший компромисс, но только если строки не слишком долго, и в этом случае он может работать даже быстрее, чем мой первый Решение.

Ответ 13

Использование foreach без inject примерно на 3% быстрее, чем при inject. Оба они намного быстрее (более 100 раз в моем опыте), чем при использовании getc.

Использование foreach без inject также может быть слегка упрощено (относительно фрагмента, указанного в другом месте в этом потоке) следующим образом:

count = 0;  File.foreach(path) { count+=1}
puts "count: #{count}"