MySQL テーブルにダンプする 300 を超えるフィールドを含む大きな CSV ファイル (〜 5 GB) の最大フィールド サイズを決定しようとしています。私が持っているファイルの CSV ファイル スキーマでは、最大フィールド長が正しくないため、テーブルのインポートでエラーが発生します。WindowsでRuby 2.0を実行しています。
配列を使用して、フィールドのインデックス (または列) の位置に従って最大フィールド長を格納しています。つまり、ヘッダー内のフィールドの実際の名前は無視しています。ハッシュ、注入、zip などを使用するなど、より手の込んだことを試してみましたが、ここでは単純な配列が最も速く動作するようです。
field_lengths[0] = Max length of first field
field_lengths[1] = Max length of second field
etc.
ファイルが大きすぎて、一度に丸呑みしたり、CSV を使用して列ごとに解析したりできません。そのため、CSV ファイルを開き、CSV#foreach を使用して各行を解析します (:headers => true オプションを使用してヘッダーを無視します)。行ごとに、フィールド値の解析済み配列をループ処理し、フィールドの長さを field_length 配列に格納されている現在の最大長と比較します。小さなファイルでこれを行うためのはるかに簡単な方法があることに気づきました。この方法は、より大きなファイルに対しては問題なく機能しますが、この方法を使用して特定のファイルの最後まで到達することはできませんでした。
ファイルを終了できないことを回避するために、現在、ヘッダー (=n) を含めて読み取る行数を定義し、n 番目の行に到達したらブレークします。以下の例では、CSV ファイルから 101 行を読み取ります。(ヘッダー行 1 行 + 実際のデータ行 100 行)。プロセスが完了していないため、ファイル内の合計行数はわかりません。
require 'csv'
require 'pp'
data_file = 'big_file.csv'
# We're only reading the first 101 lines in this example
n = 101
field_lengths = []
File.open(data_file) do |f|
CSV.foreach(f, :headers => true, :header_converters => :symbol) do |csv_row|
break if $. > n
csv_row.fields.each_with_index do |a,i|
field_lengths[i] ||= a.to_s.length
field_lengths[i] = a.to_s.length if field_lengths[i] < a.to_s.length
end
end
end
pp field_lengths
IO#read は特定のバイト数を読み取ることができますが、ファイルをバイト単位で解析すると、レコードが分割される可能性があります。CSV ファイルを小さなファイルに分割して解析するための別の提案はありますか? O'Reilly の Ruby Cookbook (Lucas Carlson & Leonard Richardson、2006 年、第 1 版) では、大きなファイルをチャンクに分割することを提案していますが (以下のように)、この例にそれを拡張する方法、特に改行を扱う方法がわかりません。等
class File
def each_chunk(chunk_size = 1024)
yield read(chunk_size) until eof?
end
end
open("bigfile.txt") do |f|
f.each_chunk(15) {|chunk| puts chunk}
end