Подсчитайте количество строк в файле, не прочитав весь файл в памяти?
Я обрабатываю огромные файлы данных (миллионы строк каждый).
Прежде чем начать обработку, я хотел бы получить количество строк в файле, поэтому я могу указать, насколько далеко продвинулась обработка.
Из-за размера файлов было бы нецелесообразно читать весь файл в памяти, просто чтобы подсчитать количество строк. Кто-нибудь имеет хорошее предложение о том, как это сделать?
Ответы
Ответ 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}"