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


391

大きなバイナリファイル(数メガバイト)をバイト配列に読み取る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;
}

60
あなたの例はに省略できますbyte[] buff = File.ReadAllBytes(fileName)
Jesse C. Slicer

3
それがサードパーティのWebサービスであるということは、ファイルがストリーミングされるのではなく、Webサービスに送信される前に完全にRAMにある必要があることを意味するのはなぜですか?Webサービスは違いを知りません。
ブライアン

@ブライアン、一部のクライアントは、Javaなどの.NETストリームの処理方法を知りません。この場合、ファイル全体をバイト配列で読み取るだけで済みます。
sjeffrey 2012年

4
@sjeffrey:私は、データは.NETストリームとして渡されるのではなく、ストリーミングされる必要があると述べました。クライアントはどちらの方法でも違いを知りません。
Brian

回答:


776

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

return File.ReadAllBytes(fileName);

ただし、メモリの消費が心配な場合は、ファイル全体を一度にメモリに読み込むことはできません。チャンクで行う必要があります。


40
この方法は2 ^ 32バイトのファイル(4.2 GB)に制限されています
Mahmoud Farahat

11
File.ReadAllBytesが大きなファイルでOutOfMemoryExceptionをスローする(630 MBのファイルでテストされ、失敗した)
sakito

6
@ juanjo.aranaええ、ええと...もちろん、メモリに収まらないものは常に存在します。その場合、質問に対する答えはありません。通常、ファイルをストリーミングし、メモリに完全に保存しないでください。一時的な対策として、これを確認することをお勧めします。msdn.microsoft.com/ en
us

4
.NETには配列サイズの制限がありますが、.NET 4.5では、特別な構成オプションを使用して大きな配列(> 2GB)のサポートをオンにできますmsdn.microsoft.com/en-us/library/hh285054.aspxを
違法-移民

3
@haragいいえ、それは質問が尋ねるものではありません。
Mehrdad Afshari 2014年

72

ここでの答えは一般に「しない」であると私は主張するかもしれません。一度にすべてのデータが絶対に必要な場合を除いて、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);
    }
}

3
ステートメントに追加するために、ファイルをクライアントにストリーミングするようなI / Oバインド操作がある場合は、非同期ASP.NETハンドラーを検討することをお勧めします。ただし、何らかの理由でファイル全体をに読み込む必要がある場合はbyte[]、ストリームなどを使用せずに、システム提供のAPIを使用することをお勧めします。
Mehrdad Afshari、2010年

@Mehrdad-同意。しかし、完全なコンテキストは明確ではありません。同様に、MVCにはこのためのアクション結果があります。
Marc Gravell

はい、一度にすべてのデータが必要です。サードパーティのWebサービスになります。
Tony_Henrich、2010年

システム提供のAPIは何ですか?
Tony_Henrich、2010年

1
@トニー:私は私の答えで述べました:File.ReadAllBytes
Mehrdad Afshari

32

私はこれを考えます:

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

3
これは、非常に大きなファイルを取得するときに停止する可能性があることに注意してください。
vapcguy

28

コードはこれに因数分解できます(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の最後の引数はバッファサイズであることにも注意してください。

FileStreamBufferedStreamについて読むこともお勧めします。

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

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


ハードウェアの種類によって違いが生じるのはなぜですか?それがIDEの場合は.NETメソッドを使用し、RAIDの場合は別の方法を使用しますか?
Tony_Henrich、2010年

@Tony_Henrich-プログラミング言語からの呼び出しとは関係ありません。ハードディスクドライブにはさまざまな種類があります。たとえば、Seagateドライブは「AS」または「NS」に分類され、NSはサーバーベースの大容量キャッシュドライブです。「AS」ドライブはコンシューマとして、ホームコンピュータベースのドライブです。シーク速度と内部転送速度も、ディスクからの読み取り速度に影響します。RAIDアレイは、キャッシングによって読み取り/書き込みパフォーマンスを大幅に向上させることができます。そのため、ファイルを一度に読み取ることはできるかもしれませんが、基盤となるハードウェアが決定的な要因です。

2
このコードには重大なバグが含まれています。読み取りは少なくとも1バイトを返すために必要です。
mafu 2012年

long to intキャストは、checked((int)fs.Length)
tzup

私はvar binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);そのusing声明の中でそうします。しかし、それは実質的にOPが行ったことと同じです。長さの値を取得して変換するのではなく、キャストfs.Lengthしてコード行を切り取ります。intlongFileInfo
vapcguy 2016年

9

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

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


管理のためのそれについてのソースコードC#、 、、garbage collector パフォーマンス、イベントカウンタ、...chunks
PreguntonCojoneroCabrón

6

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;
}

を使用するよりも優れているはずです。コメントの1つに600 MBを超えるファイルに関する問題があった.ReadAllBytes()ことを含むトップレスポンスのコメントを見た.ReadAllBytes()からBinaryReaderです。また、それをusingステートメントに入れると、FileStreamおよびBinaryReaderが確実に閉じられ、破棄されます。


C#の場合、上記の「(FileStream fs = new File.OpenRead(fileName))を使用する」ではなく、「using(FileStream fs = File.OpenRead(fileName))」を使用する必要があります。File.OpenRead()の前に新しいキーワードを削除したばかり
Syed Mohamed

@Syed上記のコードはC#用に作成されたものですが、そのnew必要はありませんでした。削除されました。
vapcguy

1

「大きなファイル」が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

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

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


BufferedStream全部を一度に読んでいるときにaを使用する意味は何ですか?
Mehrdad Afshari、2010年

彼は、ファイルを一度に読み取らないように最高のパフォーマンスを求めました。
Todd Moses

9
パフォーマンスは、操作のコンテキストで測定可能です。連続して一度にメモリに読み込んでいるストリームの追加のバッファリングは、追加のバッファの恩恵を受けることはほとんどありません。
Mehrdad Afshari、2010年

0

これを使って:

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

2
Stack Overflowへようこそ!説明はこのプラットフォームでの回答の重要な部分であるため、コードと、コードが問題の問題をどのように解決するか、なぜ他の回答よりも優れているのかを説明してください。私たちのガイド良い答えを書く方法はあなたに役立つかもしれません。おかげで
デビッド

0

概要:画像がaction =埋め込みリソースとして追加されている場合、GetExecutingAssemblyを使用してjpgリソースをストリームに取得し、ストリーム内のバイナリデータをバイト配列に読み取ります。

   public byte[] GetAImage()
    {
        byte[] bytes=null;
        var assembly = Assembly.GetExecutingAssembly();
        var resourceName = "MYWebApi.Images.X_my_image.jpg";

        using (Stream stream = assembly.GetManifestResourceStream(resourceName))
        {
            bytes = new byte[stream.Length];
            stream.Read(bytes, 0, (int)stream.Length);
        }
        return bytes;

    }

-4

私がしようとして推薦するResponse.TransferFile()、その後の方法をResponse.Flush()してResponse.End()、あなたの大きなファイルを提供するため。


-7

2 GBを超えるファイルを処理している場合、上記の方法が失敗することがわかります。

ストリームをMD5に渡してファイルをチャンクするだけで、はるかに簡単になります。

private byte[] computeFileHash(string filename)
{
    MD5 md5 = MD5.Create();
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        byte[] hash = md5.ComputeHash(fs);
        return hash;
    }
}

11
コードが質問にどのように関連しているかわかりません(または書面で提案した内容)
Vojtech B '27 / 07/27
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.