• ベストアンサー

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

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

  • ファイルを読み込むプログラムについて

    下のプログラムで分からない所がありますので、教えて頂ければと思います。宜しくお願い致します。 text = fgets(buf,256,fp);はfpのファイルから一行を読み込んでbufに格納するという処理ということは分かります。でも、while文内なので次にこの処理をする時に今度は、2行目(下の段)を読み込むはずですが、プログラム中のどこに2行目に移動させる処理があるのか分かりません。 予想ですが、text = fgets(buf,256,fp);の中にそのような意味の処理が含まれているのでしょうか? どなたかご教授お願い致します。 #include <stdio.h> int main(void) { FILE *fp; char buf[256]; char *text; char flname[256]; printf("ファイル名:"); gets(flname); fp = fopen(flname,"r"); do{ text = fgets(buf,256,fp); if(text != NULL){ printf("%s",text); } }while(text != NULL); fclose(fp); return(0); }

  • ファイルから読み込んだデータを構造体に格納できますか?

    1レコード19バイトのファイルを 読み込む処理を行っています。 地区名10バイト 県名8バイト 改行1バイト このデータをdouken(構造体)に格納したいのですが >while (fgets(dou,19,fp) != NULL){ で、エラーになってしまいます。 どのようにしたら ファイルから読み込んだデータを 構造体に格納できますか? #include<stdio.h> #include <stdlib.h> struct douken { char tiku[10]; char ken[8]; } main(void){ FILE *fp; struct douken dou[100]; int i; fp = fopen("ex3.fil","rb"); if ( fp == 0 ){ printf("can't open\n"); exit(1); } while (fgets(dou,19,fp) != NULL){ ・ ・ ・

  • どうやってフローチャートを書きますか

    #include<stdio.h>   void main()   {   FILE*fp;   char buf[128];   char *rc;   char fname[20];   do{   printf("file name>>>");   scanf("%s",fname);   fp=fopen(fname,"r");   if=(fp==NULL) printf("File Open Err¥n");   }while(fp==NULL);   rc=fgets(buf,128,fp);   while(rc!=NULL){    printf("%s",buf);    rc=fgets(buf,123,fp);    }    fclose(fp);    }

  • c言語  2つのファイルを行ごとに読み込むプログラミング

    c言語  2つのファイルを行ごとに読み込むプログラミング 0.txt と 1.txt という2つのテキストフォルダがあり 0.txt の中身は a a b b 1.txt の中身は c c d d というものとします。 これら2つのフォルダを読み込むとき まず1つのフォルダの1行目(a a)を表示し 他方の1行目(c c) 2行目(d d)を表示させて 続いて1つのフォルダの2行目(b b)を表示し 他方の1行目(c c) 2行目(d d)を表示させたいのです。 つまり実行結果が a a c c a a d d b b  ←理想の実行結果です c c b b d d となるようにしたいのですが #include <stdio.h> #include <stdlib.h> #define STR_MAX 256 int main(void) { FILE *fp, *fp2; int i, j, k; char buf[STR_MAX]; char buf2[STR_MAX]; fp = fopen("0.txt", "r"); fp2 = fopen("1.txt", "r"); if (fp == NULL && fp2 == NULL){ printf("\n"); } while(fgets(buf, STR_MAX, fp) != NULL){ while(fgets(buf2, STR_MAX, fp2) != NULL){ printf("%s%s", buf,buf2); } printf("\n"); } fclose(fp); fclose(fp2); return 0; } このプログラミングの実行結果は a a c c a a d d となり、0.txtの2行目(b b)は表示されません。 おそらく while 文 を2重にすることで 不具合が起きているのだと思うのですが 色々と調べた結果、これ以外に プログラミングが思いつきません。 私の理想の実行結果にするためには どこを訂正させると良いのでしょうか? 恐れ入りますが ご回答 どうかよろしくお願いいたします。

  • ファイルを読み込んで条件式を満たさない

    ファイルを読み込んで一部の文字列が来たら別のファイルの文字列を書き込んでもらうプログラムを作ろうとしたのですが、何故かifを使って条件分岐を試みたところ分岐してくれません。 どのようにしたら分岐しますか? 出来ればソースもお願いします。 ---ソースの内容--- #include <stdio.h> #include <string.h> void main(void){ FILE *fp,*fp2; char buf[100],buf2[100]; fp=fopen("yasa.txt","r+"); while( fgets( buf, 100, fp ) != NULL ){ if(strcmp(buf,"じゃがいも")==0){ fp2=fopen("kuda.txt","r+"); while( fgets( buf2, 100, fp2 ) != NULL ){ printf("%s",buf2); } fclose(fp2); } else{ printf("%s",buf); } } fclose(fp); } ---ソースここまで--- ---yasa.txtの内容--- きゃべつ にんじん じゃがいも だいこん セロリ ---yasa.txtここまで--- ---kuda.txtの内容--- もも オレンジ みかん ぶどう ---kuda.txtここまで---

  • ファイルの入出力に関する質問

    CSVファイルを読み込んで、処理をするプログラムを書いています。 しかし、うまくいきません。 CSVファイルは 単語1,数値データ 単語2,数値データ のようになっており、 これをsの配列に格納したいと思っています。 プログラムは以下の通りなんですが。。。 strtokはhttp://www9.plala.or.jp/sgwr-t/lib/strtok.html を参考にしました。 どなたかおしえていただけないでしょうか? #include<stdio.h> #include <string.h> int main(void) { FILE *fp; char s[1000][1000]; char tp[256]; int i=0; if((fp=fopen("in.csv","r"))==NULL){ printf("ファイルオープンできませんよ\n"); exit(1); } while(fgets(tp,256,fp)!=NULL){ tp=strtok(fp,","); puts(s[i][0]=tp); while (tp != NULL ) { tp = strtok(NULL,","); if (tp= NULL ){ puts(s[i][1]=tp); }}i++; } return(0); }

  • 同時にファイル読み込み 書き込み

    現在、ヒストグラムのプログラムを作成しています。 まず0~255の1000個の乱数ファイルdata.txtを読み込み、 ヒストグラムは出来たのですが、 エクセルでグラフを作りたいので、 data1.txtに書き込みたいので、下のソースでやってみましたが、 0~255のカウントが全部0になってしまします。 fp = fopen("data1.txt","w");が無ければ正常に処理されます。 どうか教えてください。よろしくお願いします。 #include <stdio.h> #define BUF 10 #define MAX 256 void count(FILE *fp , int* counter); int main(void) { FILE *fp; fp = fopen("data.txt","r"); fp = fopen("data1.txt","w"); int counter[MAX]; int i; for(i=0 ; i<MAX ; i++) { counter[i] = 0; } count(fp , counter); for(i=0 ; i<MAX ; i++) { printf("%d %d\n" , i, counter[i]); } fclose(fp); return 0; } void count(FILE* p_file , int* counter) { char buf[BUF]; while (fgets(buf , BUF , p_file) != NULL) { int n; sscanf(buf , "%d" , &n); counter[n]++; } }

  • ファイル

    ファイルを読み込み単語ごとに表示するプログラムです。 例 ファイル データ 形式 歴史・・ のように単語の後には空白がありますファイルです FILE *fp; char buf[1000]; char buf_word[1000]; char *str; char *bufstr; if((fp = fopen("test.txt","r")) == NULL){ printf("error!"); return 0; } while(fgets(buf,1000,fp) !=NULL){ str = buf; while(*str !='\0'){ strbuf = buf_word; if(*str ==' '){ printf("%s",buf_word); } else{ *strbuf++ = *str++; } } } とプログラムしてみましたが*strの値がどうもおかしく 最初が "フ" じゃなく"・"になってます。 最初の単語がG11とかなら"G"になっていますが・・ 教えて下さい。

  • fgetsで2行目から文字化け

    fgetsでファイルを一行ずつ読み込みたいのですが、二行目以降が文字化けしてしまいます。 ******* ソース ******* #include <windows.h> #include <stdio.h> FILE *fp; if ((fp = fopen("textlist.txt", "r")) == NULL){ MessageBox(NULL, TEXT("ファイルを開けません"), NULL, NULL); exit (1); } while (1) { TCHAR buf[128] = {0}; if (fgets(buf, sizeof(buf), fp) == NULL) break; MessageBox(NULL,buf,NULL,NULL); } fclose(fp); ***** textlist.txt ***** あいうえお かきくけこ さしすせそ メッセージボックスの一回目は正しく"あいうえお"と表示されますが、二回目・三回目は文字化けしています。 最終的に一行ずつ分けて配列に入れたいので、fgetsで出来たらと思っています。 よろしくお願いします。

  • 配列のメモリの確保

    先日メモリについてご質問させていただいたものですが、 今ファイルから読み込んだ文字列を配列に格納する作業を行なっています。 今は char buf[1000]; FILE fp; if((fp=fopen("○○.txt","r")) ==NULL){ printf("ファイルが開けません"); } while(fgets(buf,1000,fp) != NULL){ としてファイルを一行ずつ読み込んでその後単語ごとに配列に組み込みます このときファイルの文字列を格納する配列はbuf[1000]ですが このメモリでは足りないかもしれませんし多すぎるかもしれません。 足りない場合はエラーになるし多すぎる場合はメモリの無駄ですよね。 このような場合はメモリを取り直すべきなのでしょうか?その場合 どのような方法がありますか?調べてもint型の領域確保とかそういうのはあるんですがファイルから読み込んだ文字列の領域確保とかは見つからなかったので教えて下さい。