web-dev-qa-db-ja.com

大きなファイルをC#のバイト配列に読み込む最善の方法は?

私は大きなバイナリファイル(数メガバイト)をバイト配列に読み込むWebサーバーを持っています。サーバーは複数のファイルを同時に読み取ることができるため(異なるページ要求)、CPUに負担をかけずにこれを行うための最も最適化された方法を探しています。以下のコードは十分ですか?

public byte[] FileToByteArray(string fileName)
{
    byte[] buff = null;
    FileStream fs = new FileStream(fileName, 
                                   FileMode.Open, 
                                   FileAccess.Read);
    BinaryReader br = new BinaryReader(fs);
    long numBytes = new FileInfo(fileName).Length;
    buff = br.ReadBytes((int) numBytes);
    return buff;
}
348
Tony_Henrich

全体を単に次のものに置き換えます。

return File.ReadAllBytes(fileName);

ただし、メモリの消費が心配な場合は、not一度にすべてのファイルをメモリに読み込む必要があります。あなたはチャンクでそれを行う必要があります。

739
Mehrdad Afshari

私はここで答えは 一般に ではないと主張するかもしれません。 絶対に すべてのデータを一度に必要としない限り、StreamベースのAPI(または何らかのリーダー/イテレータの変形)の使用を検討してください。システム負荷を最小にし、スループットを最大にするために、(=質問で示唆されるように)複数の並列操作がある場合、それは 特に 重要です。

たとえば、呼び出し元にデータをストリーミングしているとします。

Stream dest = ...
using(Stream source = File.OpenRead(path)) {
    byte[] buffer = new byte[2048];
    int bytesRead;
    while((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) {
        dest.Write(buffer, 0, bytesRead);
    }
}
60
Marc Gravell

私はこれを考えるだろう:

byte[] file = System.IO.File.ReadAllBytes(fileName);
30
Powerlord

あなたのコードはこれを考慮に入れることができます(File.ReadAllBytesの代わりに):

public byte[] ReadAllBytes(string fileName)
{
    byte[] buffer = null;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[fs.Length];
        fs.Read(buffer, 0, (int)fs.Length);
    }
    return buffer;
} 

Integer.MaxValue - Readメソッドによるファイルサイズの制限に注意してください。言い換えれば、あなたは一度に2GBのチャンクを読むことができるだけです。

FileStreamの最後の引数はバッファサイズです。

FileStream および BufferedStream について読むことをお勧めします。

いつものように、最も速いプロファイルを作成するための簡単なサンプルプログラムが最も有益です。

基盤となるハードウェアもパフォーマンスに大きな影響を与えます。大容量キャッシュを搭載したサーバーベースのハードディスクドライブと、オンボードメモリキャッシュを搭載したRAIDカードを使用していますか?それともIDEポートに接続された標準ドライブを使用していますか?

23
user113476

操作の頻度、ファイルのサイズ、および見ているファイルの数に応じて、考慮すべきパフォーマンス上の問題が他にもあります。覚えておくべき1つのことは、あなたの各バイト配列はガベージコレクタの責任で解放されるということです。そのデータをキャッシュしていないと、大量のゴミを作成し、パフォーマンスのほとんどを %Time in GC に失うことになりかねません。チャンクが85Kより大きい場合は、解放するためにすべての世代のコレクションを必要とするラージオブジェクトヒープ(LOH)に割り当てることになります(これは非常にコストがかかり、サーバーでは実行中にすべての実行が停止します)。 )さらに、LOH上に大量のオブジェクトがある場合は、LOHの断片化(LOHが圧縮されないことになります)が発生し、パフォーマンスが低下し、メモリ不足の例外が発生する可能性があります。特定のポイントに達したらプロセスをリサイクルできますが、それがベストプラクティスかどうかはわかりません。

重要なのは、すべてのバイトを可能な限り最速でメモリに読み込む前に、アプリのライフサイクル全体を考慮する必要があるということです。そうしないと、短期的なパフォーマンスと全体的なパフォーマンスのバランスが悪くなります。

9
Joel

私はBinaryReaderは問題ないと思いますが、バッファの長さを取得するためのこれらのコード行すべてではなく、これにリファクタリングすることができます。

public byte[] FileToByteArray(string fileName)
{
    byte[] fileData = null;

    using (FileStream fs = File.OpenRead(fileName)) 
    { 
        using (BinaryReader binaryReader = new BinaryReader(fs))
        {
            fileData = binaryReader.ReadBytes((int)fs.Length); 
        }
    }
    return fileData;
}

BinaryReaderはこの種のことを意味しているので、.ReadAllBytes()を含む一番上の応答のコメントで600MBを超えるファイルに問題があることを私は見たので、.ReadAllBytes()を使用するよりも良いはずです。また、それをusingステートメントに入れると、FileStreamBinaryReaderが閉じられて破棄されます。

6
vapcguy

「ラージファイル」が4GBの制限を超えることを意味している場合は、次のコードロジックを書くのが適切です。注意すべき重要な問題は、SEEKメソッドで使用されるLONGデータ型です。 LONGは2 ^ 32データ境界を超えて指すことができます。この例では、コードは最初に1GBのチャンクで大きなファイルを処理し、大きな1GBのチャンク全体が処理された後、残りの(<1GB)バイトが処理されます。このコードは、4GBを超えるサイズのファイルのCRC計算に使用します。 (この例のcrc32c計算に https://crc32c.machinezoo.com/ を使用)

private uint Crc32CAlgorithmBigCrc(string fileName)
{
    uint hash = 0;
    byte[] buffer = null;
    FileInfo fileInfo = new FileInfo(fileName);
    long fileLength = fileInfo.Length;
    int blockSize = 1024000000;
    decimal div = fileLength / blockSize;
    int blocks = (int)Math.Floor(div);
    int restBytes = (int)(fileLength - (blocks * blockSize));
    long offsetFile = 0;
    uint interHash = 0;
    Crc32CAlgorithm Crc32CAlgorithm = new Crc32CAlgorithm();
    bool firstBlock = true;
    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        buffer = new byte[blockSize];
        using (BinaryReader br = new BinaryReader(fs))
        {
            while (blocks > 0)
            {
                blocks -= 1;
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(blockSize);
                if (firstBlock)
                {
                    firstBlock = false;
                    interHash = Crc32CAlgorithm.Compute(buffer);
                    hash = interHash;
                }
                else
                {
                    hash = Crc32CAlgorithm.Append(interHash, buffer);
                }
                offsetFile += blockSize;
            }
            if (restBytes > 0)
            {
                Array.Resize(ref buffer, restBytes);
                fs.Seek(offsetFile, SeekOrigin.Begin);
                buffer = br.ReadBytes(restBytes);
                hash = Crc32CAlgorithm.Append(interHash, buffer);
            }
            buffer = null;
        }
    }
    //MessageBox.Show(hash.ToString());
    //MessageBox.Show(hash.ToString("X"));
    return hash;
}
0
Menno de Ruiter

これを使って:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
0
Hardik Raval

パフォーマンスを向上させるには、C#のBufferedStreamクラスを使用してください。バッファは、データをキャッシュするために使用されるメモリ内のバイトのブロックであり、それによってオペレーティングシステムへの呼び出しの数を減らす。バッファは読み書き性能を向上させます。

コード例と追加の説明については、以下を参照してください。 http://msdn.Microsoft.com/ja-jp/library/system.io.bufferedstream.aspx

0
Todd Moses