我正在處理大量的數據文件(每個數百萬行)。計算文件中的行數而不將整個文件讀入內存?
在我開始處理之前,我想先計算一下文件中的行數,然後我可以指出處理的距離。
由於文件的大小,將整個文件讀入內存並不實際,只是爲了統計有多少行。有沒有人有關於如何做到這一點的好建議?
我正在處理大量的數據文件(每個數百萬行)。計算文件中的行數而不將整個文件讀入內存?
在我開始處理之前,我想先計算一下文件中的行數,然後我可以指出處理的距離。
由於文件的大小,將整個文件讀入內存並不實際,只是爲了統計有多少行。有沒有人有關於如何做到這一點的好建議?
如果你在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
使用什麼語言並不重要,如果行長度可變,您將不得不讀取整個文件。這是因爲換行符可能在任何地方,沒有閱讀文件就無法知道(假設它沒有被緩存,一般來說它不是)。
如果你想表明進度,你有兩個現實的選擇。您可以根據假設的行長推斷進度:
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%
這應該足夠了。
其實我需要這可能是一個更好的主意是計數的行數。 – smnirven 2010-04-16 13:45:36
我假設原始海報可以通過文件讀取,只是沒有在內存中的全部內容。 – 2010-04-22 23:18:58
讀取文件在一個時間線:
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
最後一個是最乾淨的,因爲我們可以假設「wc」針對良好的I/O速度進行了優化。 「.split.first」是多餘的,不要忘記在文件名周圍添加單引號,否則它將在具有空格的文件名上失敗。簡化:%x {wc -l'#{filename}'}。to_i – deafgreatdane 2011-08-18 18:17:55
or'count =%x {wc -l <「#{filename}」}。to_i' – 2012-04-24 16:33:46
@deafgreatdane我不認爲'wc'是「乾淨的」。現在它不會在Windows上運行..我會採取小的性能下降(在相同的複雜度級別內)以避免可移植性問題。 – 2013-03-07 17:42:01
的原因,我不完全理解,掃描使用File
換行符的文件似乎是一個速度遠比做CSV#readlines.count
。
以下基準中使用CSV文件與1045574線的數據的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
正如你可以看到,掃描換行符的文件速度要快一個數量級。
內存中的掃描力和文件對象被泄漏。 – 2013-03-07 17:44:08
對於10M行文件,我看到IOError:文件太大。 – 2013-03-15 00:21:21
'File.read'加載它並對它做任何事情。 'CSV.open(...)。readlines'讀取並解析每行到數組中。你正在比較蘋果和橙子,所以當然,速度會有很大的差異。 – 2013-04-18 18:58:08
如果該文件是一個CSV文件,如果該文件的內容是數字,則記錄的長度應該非常均勻。將文件的大小除以記錄的長度或前100條記錄的平均值是否有意義?
這個*應該*返回一個在球場某處的值。 – 2013-04-18 18:58:54
同DJ的答案,而是讓實際的Ruby代碼:
count = %x{wc -l file_path}.split[0].to_i
第一部分
wc -l file_path
給你
num_lines file_path
的split
和to_i
它放入一個數。
使用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代碼看起來像。希望你的dain沒有受到Java的壓制。
順便說一句,千萬不要考慮任何使用IO#read
或IO#readlines
方法反過來呼籲什麼被讀取 字符串方法上面 的答案。你說你不想 將整個文件讀入內存,這正是它們所做的。 這就是爲什麼唐納德克努特建議人們瞭解如何編程 更接近硬件,因爲如果他們不這樣做,他們最終會寫 「怪異的代碼」。很顯然,當你不需要時,你不想編寫接近於 的硬件,但這應該是常識。 但是,您應該學會識別您具有 的實例,以便更接近像這樣的螺母和螺栓。
並且不要試圖獲得比 要求更多的「面向對象」。對於想要看起來比實際更復雜的新手來說,這是一個令人尷尬的陷阱。你應該總是很高興 爲答案真的很簡單,而不是 失望,當沒有複雜的給你機會 寫「令人印象深刻」的代碼。但是,如果你想看起來有點 「面向對象」,而且不介意每次讀整條生產線到 存儲器(即,你知道線足夠短),你 可以做到這一點
f = File.new("/path/to/whatever")
num_newlines = 0
f.each_line do
num_newlines += 1
end
這將是一個很好的折衷方案,但前提是線條長度不會太長,在這種情況下,它甚至可能比我的第一個 解決方案運行得更快。
雖然這有一些很好的信息,例如「不要試圖獲得更多」面向對象的「比情況要求」,但代碼並非最佳實踐:您無法關閉文件句柄。要麼明確地執行它,要麼使用'File.open'的塊來讓Ruby爲你做。但是,在內部使用帶有計數器的'File.foreach'會更簡單,而且速度更快。 – 2013-12-18 17:07:30
使用紅寶石:
file=File.open("path-to-file","r")
file.readlines.size
39毫秒快然後WC -l上的325.477行文件
你的系統的'wc'有問題。 – 2014-05-05 19:28:00
我相當肯定你不能用一個巨大的文件做這件事,因爲它的內容將完全保存在內存中。 – pisaruk 2014-09-29 00:43:09
是的,''readlines'加載內存中的所有行。 – 2015-02-09 22:54:19
使用foreach
而不inject
大於與inject
快約3%。兩者都比使用getc
快得多(以我的經驗計算超過100倍)。
使用foreach
而不inject
也可以略微簡化的(相對於在此線程別處給出的片斷),如下所示:
count = 0; File.foreach(path) { count+=1}
puts "count: #{count}"
我有這樣一個襯裏。
puts File.foreach('myfile.txt').count
只有在將整個文件加載到系統內存的情況下才可以。使用'wc'可以更好地縮放大文件。 – 2017-10-05 13:41:26
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
超過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,當你多計一行。
摘要發佈的解決方案
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)
WC太快了,可能不需要進度計數器。 – 2010-04-16 05:36:24
有一個邊緣條件:如果文件的最後一行沒有換行符,wc會出現一條短路。這是通過posix設計,請參閱http:// backreference。org/2010/05/23/sanitizing-files-with-no-trailing-newline/ – deafgreatdane 2011-10-19 17:11:21
請做的不僅僅是引用方法,在實踐中引用了wc -l的示例。不是每個人都知道對你而言顯而易見的事情。 (我知道,「Google更努力!」但是如果我們都可以更像Ruby,我們會本能地做到這一點。) – 2012-07-20 07:02:22