1 GB を超えるような csv ファイルを一度に読み込むとメモリエラーが発生する場合がある
さらに処理時間もすごくかかるため、ストリーム処理に変更などして処理の高速化した際の方法を備忘録として残しておく
方法
File#open メソッドと CSV#new メソッドを組み合わせて実現する
環境
- ruby: 3.0.4
- rails: 6.1.6.1
実際のコード
csv ファイルの特定の文字列を置換する処理
前提
header 付の csv ファイル
リファクタリング前
class Converter
def self.convert
# 読み取り専用でファイルを開く
read_only_file = File.open("{ファイルパス}" , "r")
# 保存用バッファ
buffer = read_only_file.read()
# バッファの中身を変換
buffer.gsub!({置換対象文字列}, {置換文字列})
# ファイルを書き込みモードで開き直す
file = File.open("{ファイルパス}" , "w")
# 変更内容を出力する
file.write(buffer)
# close
read_only_file.close()
file.close()
end
end
リファクタリング後
require 'csv'
require 'fileutils'
class Converter
def self.convert
file_basename = "{拡張子を除いたファイル名}"
# 読み取り専用でファイルを開く
File.open("#{file_basename}.csv", "r") do |file|
break if File.zero?("#{file_basename}.csv")
# 一時ファイルを書き込みモードで開く
write_file = File.open("#{file_basename}_tmp.csv", "w")
# csv の header を書き込む
write_file.write(file.readline)
file.rewind
CSV.new(file, headers: true).each do |row|
# 読み込んだ行内の文字列を置換する
write_file.write(row.to_s.gsub({置換対象文字列}, {置換文字列}))
end
write_file.close
end
FileUtils.move("#{file_basename}_tmp.csv", "#{file_basename}.csv") if File.exist?("#{file_basename}_tmp.csv")
end
end
ヘッダは force quote なし ボディは force quote ありのようにしたい場合
id,名前
"1","太郎"
CSV#open メソッドを使用する
require 'csv'
require 'fileutils'
class Converter
def self.convert
file_basename = "{拡張子を除いたファイル名}"
# 読み取り専用でファイルを開く
File.open("#{file_basename}.csv", "r") do |file|
break if File.zero?("#{file_basename}.csv")
# 一時ファイルを書き込みモードで開く
CSV.open("#{file_basename}_tmp.csv", "w") do |csv|
# csv の header を書き込む
csv << file.readline.parse_csv
end
file.rewind
# 一時ファイルを追記モードで開く
CSV.open("#{file_basename}_tmp.csv", "a", force_quotes: true) do |csv|
CSV.new(file, headers: true).each do |row|
# 読み込んだ行内の文字列を置換する
csv << row.to_s.gsub({置換対象文字列}, {置換文字列}).parse_csv
end
end
end
FileUtils.move("#{file_basename}_tmp.csv", "#{file_basename}.csv") if File.exist?("#{file_basename}_tmp.csv")
end
end
1.4 GB 程の csv ファイルを処理した際にリファクタリング前は、1 時間 40 分程度だったものが 5 分程度の処理時間に改善した