Rubyで高速CSV解析(FasterCSVの2倍〜4倍)
Rubyの標準CSV解析が遅い。かといってFasterCSVを入れるのもいまいち・・。
ということで高速にCSV解析するコードを作成した。
PureRubyながら、FasterCSVの2倍ほど速い。
使用パーサ | user | system | total | real |
---|---|---|---|---|
自作パーサ | 13.313000 | 0.140000 | 13.453000 | ( 13.515000) |
FasterCSV | 28.937000 | 0.094000 | 29.031000 | ( 29.141000) |
テスト用データは郵便番号一覧の全国版。約10万レコード。
http://www.post.japanpost.jp/zipcode/dl/oogaki.html
1行が長くてカラム数が多いデータの場合は、もっと速くなる。
平均1000Byteでカラム数が150個ほどあるCSVファイル10,000行を処理させたときで、4倍程度速い。
使用パーサ | user | system | total | real |
---|---|---|---|---|
自作パーサ | 4.562000 | 0.063000 | 4.625000 | ( 4.656000) |
FasterCSV | 17.579000 | 0.031000 | 17.610000 | ( 17.656000) |
使い方は標準CSVのforeachと同じ。
require 'k_csv' CsvFile = 'KEN_ALL.CSV' KCSV::foreach(CsvFile) do |row| p row end
コードは以下。これを "k_csv.rb" として保存する。
# Ruby標準添付のCSVが遅すぎなので作成。 # - ""内に改行がある場合も対応。 # - ,,の場合はnil、,"",の場合は空文字列""になる。 # # ToDo # - ヘッダ解析&row[ヘッダ名]でアクセスできるようにする。 # - 例外チェックするようにする。 # class KCSV def KCSV.foreach(path, rs = nil, &block) open_reader(path, 'r', ',', rs, &block) end class << self private @@quote_mark = ?" def open_reader(path, mode, fs, rs, &block) file = File.open(path, mode) if block begin parse_file(file, fs, rs, &block) ensure file.close end end end def parse_file(file, fs, rs, &block) in_quote = false while line = file.gets do cols = [] unless in_quote cols_org = line.split(fs) cols_org[-1].chomp! cols_org.each do |column| if not in_quote if column.size == 0 cols.push(nil); else # column.size > 0 if column[0] == @@quote_mark if (column.size > 1) && (column[-1] == @@quote_mark) cols.push(column[1..-2].gsub('""','"')) else cols.push(column[1..-1]) in_quote = true end else # column[0] != @@quote_mark cols.push(column) end end else #in_quote #行の最初がクォート中である場合は、前回との区切りは #","ではなく改行である。 separator = cols_org[0].equal?(column) ? "\n" : fs # 末尾が"の場合は基本的にquoteの出口だが、末尾が""の場合は # エスケープされるので、まだ出口ではない。 if column[-1] == @@quote_mark && column[-2] != @@quote_mark cols[-1] = cols[-1] + separator + column[0..-2] cols[-1].gsub!('""', '"') in_quote = false else cols[-1] = cols[-1] + separator + column end end end yield(cols) unless in_quote end end end end