• ベストアンサー

巨大なテキストファイル(可変長)を効率よく読込む方法は?

Borland C++ Builder 5 を使っています。 30万件以上のレコードが格納されたCSVファイルを読込むプログラムを作っています。 1件当りのレコード長は可変です。(MAX値は余裕を見て200バイトくらい。) 1件ごと決められた処理をする必要がありますので、次のようなソースを書きました。 while(fgets(buf,200,fp31) != NULL){ //CSVの分解とデータ処理 } しかし、さすがに30万件は時間がかかります。 まとめてドッカンと読込む方法もあろうかと思うのですが、1件ごと処理をするためにはどうしたら良いか分かっていません。 何かうまい方法はないものでしょうか? ご指導いただければ幸いです。

質問者が選んだベストアンサー

  • ベストアンサー
  • jacta
  • ベストアンサー率26% (845/3158)
回答No.5

CSVはまともに処理しようと思うと、fgets等を使って行単位で読み込むべきではありません。 というのは、フィールド内に改行(CRLF)が含まれるケースがあるからです。この場合は二重引用符でエスケープされます。 ちなみに、CSVの仕様(RFC4180)に合致していることを期待してよいのであれば、改行は必ずCRLFですし、ASCII以外の文字が含まれることも想定する必要はありません。 標準ライブラリのストリームは、(少なくともBorland C++ Compilerに限れば)最も効率がよいであろうサイズでバッファリングされているはずですので、そこを調整しても大した影響は出ないでしょう。 それより、パーサーを高速化するほうが得策かと思います。

参考URL:
http://www.ietf.org/rfc/rfc4180.txt
Han1344
質問者

お礼

ありがとうございました。変身が遅くなり申し訳ありません。 いくつかのテストをしてみました。 単純に、fgets()で行単位に読むか、fread()でブロック単位に読むかの比較です。 実際の容量75Mくらいのファイルを使いました。 行単位で2秒、ブロック単位で1秒でした。 (少数以下の秒は表示していない。) 目で見ている感覚では、ブロック単位は1秒以下という感じです。 以上はローカルディスクでの話です。 これがネットワーク経由で読み込むと、行単位は25秒くらいになりました。 ただ、今回の仕組みはローカルでの運用ですから、まあ、ほとんど変わりない、という結論でも良いかもしれません。 なお、CSVについては、市販のあるパッケージソフトが吐き出すものです。 それぞれのフィールドは、そのパッケージに入力した項目や、マスター類の項目がほとんどです。 入力項目に複数行を認めるものはありません。 従ってシロート判断ですが、フィールド内に改行は無い、と考えています。 ただし日本語フィールドはあります。 結論としては、改造はやめようと思います。

その他の回答 (5)

  • titokani
  • ベストアンサー率19% (341/1726)
回答No.6

>まとめてドッカンと読込む方法もあろうかと思うのですが どこに時間がかかっているのか調べるのが先でしょう。 ファイル読み込みなのか、CSVの分解とデータ処理なのか。 #5さんもおっしゃっていますが、ストリーム入力はもともとバッファリングが行われていますので、まとめて読んだとしても、特に効果はない可能性が高いです。

Han1344
質問者

お礼

ありがとうございました。 No5のお礼にも書きましたが、行単位の読込みが遅い原因ではないようです。 データ処理の部分で時間がかかっているようでした。

noname#208124
noname#208124
回答No.4

fopenのmodeに"S"とか_openのoflagに_O_SEQUENTIALを付けて先読みに期待させる

Han1344
質問者

お礼

ありがとうございました。 fopenのmodeの"s"というのが分かりませんでした。 fopen(FLNM,"rs")としてみましたが、コンパイルは通ったのですが、実行時にOPENできない、というエラーになりました。 せっかくアドバイスいただいたのに生かすことが出来ずに申し訳ありませんでした。

  • S117
  • ベストアンサー率40% (18/45)
回答No.3

setvbuf(fp, NULL, _IOFBF, 1000000); とりあえずこれをfopen直後に入れて、パフォーマンスの変化を確認してください。詳細はsetvbufで検索するなり、手元の資料なりで調べてみてください。

回答No.2

 レコードサイズが今度余り増えないようなら一気に読み込んでしまっても いいのですが、そうでないのであれば、1MB~数MBくらいのバッファに 一度途中まで読み込み、1文字ずつ解析をします。  1回の読み込みではまだファイルにデータが残っていることが多く、 その場合メモリにある最後の行のレコードも途中である可能性もあるので そこの繋ぎ部分は注意して作る必要がありますが、この方法なら それほどファイル読み込みも負荷にはならない可能性が高いですし、 メモリ的に大丈夫でしょう。  で、1行毎の解析ですが、まず分割という処理の必要性が疑問です。  頭から解析し、1つ1つの","で区切られた文字を見て、改行があればそこで1レコード終了になります。  なので、明示的に分割して何かするという処理は要らないです。 >この場合の改行コードは16進表記で「0d0a」ですよね  0x0aだけかもしれませんし、0x0dだけかもしれません。  0x0d/0x0aと連続で来ることを期待して作るとバグを生むかもしれません。 >前回の出現位置から今回の出現位置までをbufにCOPYする。  ファイルからメモリに読み込んだ段階でそこにテキストがあるので これは要りません。(ASCII->UNICODE変換など変換があるなら別ですが)

Han1344
質問者

お礼

PROMETHEUSさん、ありがとうございました。 >で、1行毎の解析ですが、まず分割という処理の必要性が疑問です。 これは、単純に今のプログラム(CSVを分解してデータ処理を行う)がそのまま利用できる、というだけの理由です。 なるほど、頭からCSVを分割してしまう発想はありませんでした。 > 0x0aだけかもしれませんし、0x0dだけかもしれません。 > 0x0d/0x0aと連続で来ることを期待して作るとバグを生むかもしれません。 そうなんですか、Windous系の場合(今はNTFSですが)すべて0x0d/0x0aと連続で来ると思っていました。 ちょっと厄介ですね。 >その場合メモリにある最後の行のレコードも途中である可能性もあるので >そこの繋ぎ部分は注意して作る必要がありますが、 これは漠然とそんなことも考えていました。 やはり自分で何とかしないといけないのですね。 やはりスピードを上げるとなると、厄介なことが多いですね。

  • phoenix343
  • ベストアンサー率15% (296/1946)
回答No.1

30万件ですかー 単純に200×30万=57MB弱 結構大きいね 単純に考えるなら一行ずつ読み込むのではなく いったんファイルの内容全部をメモリに読み込む方法が考えられます。 その後、改行コードで分割して、一行ずつ解析する処理になるかと。 ※ファイルにアクセスするのって結構時間がかかるんです。なるべく少なく。。

Han1344
質問者

お礼

phoenix343さん、ありがとうございました。 メモリーに読込んだデータを改行コードで分割する方法ですが、この場合の改行コードは16進表記で「0d0a」ですよね? (1)メモリーの頭から1バイトずつ「0d0a」が出現するか判定する。 (2)出現したら、前回の出現位置から今回の出現位置までをbufにCOPYする。 (3)bufのデータ処理を行った後、今回の出現位置から後方向に「0d0a」を探す。 というような方法が思い浮かびます。 ちょっとスマートでないような気もします。 まあ、泥臭い方法の方が確実で誰にでも分かりやすい、ということはあると思うのですが・・・。

関連するQ&A