6

C# を使用して、約 120 MB のプレーンテキスト CSV ファイルを読み込んでいます。最初は行ごとに読み取って解析を行いましたが、最近、ファイルの内容全体を最初にメモリに読み取る方が数倍高速であることがわかりました。CSV には引用符の中にコンマが埋め込まれているため、解析はすでに非常に低速です。つまり、正規表現の分割を使用する必要があります。これは、確実に機能することがわかった唯一のものです。

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,)
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))");
// from http://regexlib.com/REDetails.aspx?regexp_id=621

内容全体をメモリに読み取った後に解析を行うために、改行文字で文字列分割を行い、各行を含む配列を取得します。ただし、120 MB のファイルでこれを行うと、System.OutOfMemoryException. コンピューターに 4 GB の RAM が搭載されているのに、メモリがすぐに不足してしまうのはなぜですか? 複雑な CSV をすばやく解析するより良い方法はありますか?

4

9 に答える 9

8

必要がない限り、独自のパーサーをロールしないでください。私はこれで運が良かった:

高速 CSV リーダー

他に何もない場合は、ボンネットの下を見て、他の誰かがそれをどのように行っているかを見ることができます.

于 2009-04-30T21:34:58.900 に答える
7

基本的にどのサイズの割り当てでも OutOfMemoryException を取得できます。メモリを割り当てるときは、要求されたサイズの連続したメモリを実際に要求しています。それが守られない場合は、OutOfMemoryException が表示されます。

また、64 ビットの Windows を実行していない限り、4 GB の RAM が 2 GB のカーネル空間と 2 GB のユーザー空間に分割されるため、.NET アプリケーションはデフォルトで 2 GB 以上にアクセスできないことにも注意してください。

.NET で文字列操作を行う場合、.NET 文字列は不変であるため、大量の一時文字列が作成される危険があります。したがって、メモリ使用量が大幅に増加することがあります。

于 2009-04-30T21:30:07.477 に答える
5

ファイル全体を文字列に読み込む場合は、おそらくStringReaderを使用する必要があります。

StringReader reader = new StringReader(fileContents);
string line;
while ((line = reader.ReadLine()) != null) {
    // Process line
}

これは、ファイルからのストリーミングとほぼ同じですが、コンテンツが既にメモリにあるという違いがあります。

テスト後に編集

処理がline.Lengthで長さ変数をインクリメントすることで構成された140MBのファイルで上記を試しました。これには、私のコンピューターで約 1.6 秒かかりました。この後、次のことを試しました。

System.IO.StreamReader reader = new StreamReader("D:\\test.txt");
long length = 0;
string line;
while ((line = reader.ReadLine()) != null)
    length += line.Length;

結果は約1秒でした。

もちろん、ネットワーク ドライブから読み込んでいる場合や、処理にハード ドライブが別の場所を探すのに十分な時間がかかる場合は特に、マイレージは異なる可能性があります。ただし、FileStream を使用してファイルを読み取り、バッファリングしていない場合も同様です。StreamReader は、読み取りを大幅に強化するバッファリングを提供します。

于 2009-04-30T21:33:56.937 に答える
4

単一のオブジェクトにそれほど多くの連続したメモリを割り当てることはできないかもしれませんし、できると期待するべきではありません。ストリーミングはこれを行うための通常の方法ですが、遅くなる可能性があることは間違いありません (ただし、通常はそれほど遅くなるべきではないと思います)。

妥協案として、 のような関数を使用して、一度にファイルのより大きな部分 (ただし全体ではない) を読み取り、StreamReader.ReadBlock()各部分を順番に処理することを試みることができます。

于 2009-04-30T21:30:35.323 に答える
1

他のポスターが言うように、OutOfMemory は、要求されたサイズのメモリの連続したチャンクが見つからないためです。

ただし、一度にすべてを読み取ってから処理を行うよりも、1 行ずつ解析を行う方が数倍高速だったとおっしゃっています。これは、たとえば (疑似コードで) 読み取りをブロックする単純なアプローチを追求している場合にのみ意味があります。

while(! file.eof() )
{
    string line = file.ReadLine();
    ProcessLine(line);
}

代わりにストリーミングを使用する必要があります。ストリームは、ファイルを読み取っている代替スレッドからの Write() 呼び出しによって埋められるため、ProcessLine() が行うことによってファイルの読み取りがブロックされることはありません。これは、ファイル全体を一度に読み取ってから処理を実行するパフォーマンスと同等である必要があります。

于 2009-04-30T21:43:03.453 に答える
0

チャンクをバッファーに読み込んで作業する必要があります。次に、別のチャンクなどを読み取ります。

これを効率的に行うライブラリはたくさんあります。私はCsvHelperと呼ばれるものを維持しています。カンマや行末がフィールドの真ん中にある場合など、処理する必要のあるエッジケースはたくさんあります。

于 2010-02-22T23:52:26.780 に答える
0

私はここのほとんどの人に同意します、あなたはストリーミングを使う必要があります。

これまでに誰かが言ったかどうかはわかりませんが、拡張方法を検討する必要があります。

そして、確かに、.NET/CLRで最高のCSV分割手法は これです。

この手法により、入力CSVから+ 10GBのXML出力が生成されました。これには、広範な入力フィルターなどが含まれ、これまでに見たものよりも高速です。

于 2009-05-15T08:32:26.820 に答える
0

実際のメモリ使用量を判断するには、おそらくCLR プロファイラーを試してみてください。システム RAM 以外のメモリ制限がある可能性があります。たとえば、これが IIS アプリケーションの場合、メモリはアプリケーション プールによって制限されます。

このプロファイル情報を使用すると、最初に試みた CSV ファイルのストリーミングなど、よりスケーラブルな手法を使用する必要があることがわかる場合があります。

于 2009-04-30T21:31:51.887 に答える