2010-04-16 94 views
44

我正在處理大量的數據文件(每個數百萬行)。計算文件中的行數而不將整個文件讀入內存?

在我開始處理之前,我想先計算一下文件中的行數,然後我可以指出處理的距離。

由於文件的大小,將整個文件讀入內存並不實際,只是爲了統計有多少行。有沒有人有關於如何做到這一點的好建議?

回答

58

如果你在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 
+3

WC太快了,可能不需要進度計數器。 – 2010-04-16 05:36:24

+6

有一個邊緣條件:如果文件的最後一行沒有換行符,wc會出現一條短路。這是通過posix設計,請參閱http:// backreference。org/2010/05/23/sanitizing-files-with-no-trailing-newline/ – deafgreatdane 2011-10-19 17:11:21

+5

請做的不僅僅是引用方法,在實踐中引用了wc -l的示例。不是每個人都知道對你而言顯而易見的事情。 (我知道,「Google更努力!」但是如果我們都可以更像Ruby,我們會本能地做到這一點。) – 2012-07-20 07:02:22

13

使用什麼語言並不重要,如果行長度可變,您將不得不讀取整個文件。這是因爲換行符可能在任何地方,沒有閱讀文件就無法知道(假設它沒有被緩存,一般來說它不是)。

如果你想表明進度,你有兩個現實的選擇。您可以根據假設的行長推斷進度:

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% 

這應該足夠了。

+0

其實我需要這可能是一個更好的主意是計數的行數。 – smnirven 2010-04-16 13:45:36

+1

我假設原始海報可以通過文件讀取,只是沒有在內存中的全部內容。 – 2010-04-22 23:18:58

68

讀取文件在一個時間線:

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 
+5

最後一個是最乾淨的,因爲我們可以假設「wc」針對良好的I/O速度進行了優化。 「.split.first」是多餘的,不要忘記在文件名周圍添加單引號,否則它將在具有空格的文件名上失敗。簡化:%x {wc -l'#{filename}'}。to_i – deafgreatdane 2011-08-18 18:17:55

+4

or'count =%x {wc -l <​​「#{filename}」}。to_i' – 2012-04-24 16:33:46

+4

@deafgreatdane我不認爲'wc'是「乾淨的」。現在它不會在Windows上運行..我會採取小的性能下降(在相同的複雜度級別內)以避免可移植性問題。 – 2013-03-07 17:42:01

2

的原因,我不完全理解,掃描使用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 

正如你可以看到,掃描換行符的文件速度要快一個數量級。

+0

內存中的掃描力和文件對象被泄漏。 – 2013-03-07 17:44:08

+0

對於10M行文件,我看到IOError:文件太大。 – 2013-03-15 00:21:21

+1

'File.read'加載它並對它做任何事情。 'CSV.open(...)。readlines'讀取並解析每行到數組中。你正在比較蘋果和橙子,所以當然,速度會有很大的差異。 – 2013-04-18 18:58:08

1

如果該文件是一個CSV文件,如果該文件的內容是數字,則記錄的長度應該非常均勻。將文件的大小除以記錄的長度或前100條記錄的平均值是否有意義?

+1

這個*應該*返回一個在球場某處的值。 – 2013-04-18 18:58:54

2

同DJ的答案,而是讓實際的Ruby代碼:

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

第一部分

wc -l file_path 

給你

num_lines file_path 

splitto_i它放入一個數。

0

使用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#readIO#readlines方法反過來呼籲什麼被讀取 字符串方法上面 的答案。你說你不想 將整個文件讀入內存,這正是它們所做的。 這就是爲什麼唐納德克努特建議人們瞭解如何編程 更接近硬件,因爲如果他們不這樣做,他們最終會寫 「怪異的代碼」。很顯然,當你不需要時,你不想編寫接近於 的硬件,但這應該是常識。 但是,您應該學會識別您具有 的實例,以便更接近像這樣的螺母和螺栓。

並且不要試圖獲得比 要求更多的「面向對象」。對於想要看起來比實際更復雜的新手來說,這是一個令人尷尬的陷阱。你應該總是很高興 爲答案真的很簡單,而不是 失望,當沒有複雜的給你機會 寫「令人印象深刻」的代碼。但是,如果你想看起來有點 「面向對象」,而且不介意每次讀整條生產線到 存儲器(即,你知道線足夠短),你 可以做到這一點

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

這將是一個很好的折衷方案,但前提是線條長度不會太長,在這種情況下,它甚至可能比我的第一個 解決方案運行得更快。

+0

雖然這有一些很好的信息,例如「不要試圖獲得更多」面向對象的「比情況要求」,但代碼並非最佳實踐:您無法關閉文件句柄。要麼明確地執行它,要麼使用'File.open'的塊來讓Ruby爲你做。但是,在內部使用帶有計數器的'File.foreach'會更簡單,而且速度更快。 – 2013-12-18 17:07:30

8

使用紅寶石:

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

39毫秒快然後WC -l上的325.477行文件

+0

你的系統的'wc'有問題。 – 2014-05-05 19:28:00

+1

我相當肯定你不能用一個巨大的文件做這件事,因爲它的內容將完全保存在內存中。 – pisaruk 2014-09-29 00:43:09

+1

是的,''readlines'加載內存中的所有行。 – 2015-02-09 22:54:19

0

使用foreach而不inject大於與inject快約3%。兩者都比使用getc快得多(以我的經驗計算超過100倍)。

使用foreach而不inject也可以略微簡化的(相對於在此線程別處給出的片斷),如下所示:

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

我有這樣一個襯裏。

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

只有在將整個文件加載到系統內存的情況下才可以。使用'wc'可以更好地縮放大文件。 – 2017-10-05 13:41:26

2

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 
1

超過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,當你多計一行。

3

摘要發佈的解決方案

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) 
相關問題