Cara terbaik untuk membaca file besar ke dalam byte array di C #?

391

Saya memiliki server web yang akan membaca file biner besar (beberapa megabita) ke dalam byte array. Server dapat membaca beberapa file secara bersamaan (permintaan halaman berbeda), jadi saya mencari cara yang paling optimal untuk melakukan ini tanpa membebani CPU terlalu banyak. Apakah kode di bawah ini cukup baik?

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;
}
Tony_Henrich
sumber
60
Contoh Anda dapat disingkat byte[] buff = File.ReadAllBytes(fileName).
Jesse C. Slicer
3
Mengapa ini menjadi layanan web pihak ketiga menyiratkan file harus sepenuhnya dalam RAM sebelum dikirim ke layanan web, daripada streaming? Layanan web tidak akan tahu bedanya.
Brian
@Brian, Beberapa klien tidak tahu bagaimana menangani aliran .NET, seperti Java misalnya. Ketika hal ini terjadi, semua yang dapat dilakukan adalah membaca seluruh file dalam byte array.
sjeffrey
4
@ sjeffrey: Saya katakan data harus di-stream, bukan diteruskan sebagai aliran .NET. Klien tidak akan tahu bedanya.
Brian

Jawaban:

776

Cukup ganti semuanya dengan:

return File.ReadAllBytes(fileName);

Namun, jika Anda khawatir tentang konsumsi memori, Anda tidak harus membaca keseluruhan file sekaligus. Anda harus melakukannya dalam potongan.

Mehrdad Afshari
sumber
40
metode ini terbatas pada 2 ^ 32 byte file (4,2 GB)
Mahmoud Farahat
11
File.ReadAllBytes melempar OutOfMemoryException dengan file besar (diuji dengan file 630 MB dan gagal)
sakito
6
@ juanjo.arana Ya, well ... tentu saja akan selalu ada sesuatu yang tidak sesuai dengan memori, dalam hal ini, tidak ada jawaban untuk pertanyaan itu. Secara umum, Anda harus melakukan streaming file dan tidak menyimpannya di memori sama sekali. Anda mungkin ingin melihat ini sebagai ukuran sementara: msdn.microsoft.com/en-us/library/hh285054%28v=vs.110%29.aspx
Mehrdad Afshari
4
Ada batasan untuk ukuran array di .NET, tetapi dalam .NET 4.5 Anda dapat mengaktifkan dukungan untuk array besar (> 2GB) menggunakan opsi konfigurasi khusus lihat msdn.microsoft.com/en-us/library/hh285054.aspx
ilegal -immigrant
3
@harag Tidak, dan bukan itu pertanyaannya.
Mehrdad Afshari
72

Saya mungkin berpendapat bahwa jawaban di sini umumnya adalah "jangan". Kecuali Anda benar-benar membutuhkan semua data sekaligus, pertimbangkan untuk menggunakan StreamAPI berbasis (atau varian pembaca / iterator). Itu sangat penting ketika Anda memiliki beberapa operasi paralel (seperti yang disarankan oleh pertanyaan) untuk meminimalkan beban sistem dan memaksimalkan throughput.

Misalnya, jika Anda mengalirkan data ke penelepon:

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);
    }
}
Marc Gravell
sumber
3
Untuk menambah pernyataan Anda, saya bahkan menyarankan mempertimbangkan penangan ASP.NET async jika Anda memiliki operasi terikat I / O seperti streaming file ke klien. Namun, jika Anda harus membaca seluruh file ke byte[]karena suatu alasan, saya sarankan menghindari menggunakan stream atau apa pun dan hanya menggunakan sistem yang disediakan API.
Mehrdad Afshari
@Mehrdad - setuju; tetapi konteks lengkapnya tidak jelas. MVC juga memiliki hasil tindakan untuk ini.
Marc Gravell
Ya saya membutuhkan semua data sekaligus. Ini akan menjadi layanan web pihak ketiga.
Tony_Henrich
Apa sistem yang disediakan API?
Tony_Henrich
1
@Tony: Saya dinyatakan dalam jawaban saya: File.ReadAllBytes.
Mehrdad Afshari
32

Saya akan memikirkan ini:

byte[] file = System.IO.File.ReadAllBytes(fileName);
Powerlord
sumber
3
Perhatikan bahwa ini dapat terhenti ketika mendapatkan file yang sangat besar.
vapcguy
28

Kode Anda dapat diperhitungkan untuk ini (sebagai pengganti 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;
} 

Perhatikan Integer.MaxValue - batasan ukuran file yang ditempatkan oleh metode Baca. Dengan kata lain Anda hanya dapat membaca potongan 2GB sekaligus.

Perhatikan juga bahwa argumen terakhir untuk FileStream adalah ukuran buffer.

Saya juga menyarankan membaca tentang FileStream dan BufferedStream .

Seperti biasa, contoh program sederhana untuk profil yang tercepat akan sangat bermanfaat.

Perangkat keras Anda juga akan memiliki efek besar pada kinerja. Apakah Anda menggunakan hard disk drive berbasis server dengan cache besar dan kartu RAID dengan cache memori terpasang? Atau apakah Anda menggunakan drive standar yang terhubung ke port IDE?


sumber
Mengapa jenis perangkat keras akan membuat perbedaan? Jadi jika itu IDE Anda menggunakan beberapa metode .NET dan jika itu RAID Anda menggunakan yang lain?
Tony_Henrich
@ Tony_Henrich - Tidak ada hubungannya dengan panggilan apa yang Anda lakukan dari bahasa pemrograman Anda. Ada berbagai jenis hard disk drive. Misalnya, drive Seagate diklasifikasikan sebagai "AS" atau "NS" dengan NS sebagai server, cache cache besar di mana-sebagai drive "AS" adalah drive berbasis komputer rumah konsumen. Mencari kecepatan dan kecepatan transfer internal juga memengaruhi seberapa cepat Anda dapat membaca sesuatu dari disk. Array RAID dapat sangat meningkatkan kinerja baca / tulis melalui caching. Jadi Anda mungkin dapat membaca file sekaligus, tetapi perangkat keras yang mendasarinya masih merupakan faktor penentu.
2
Kode ini mengandung bug penting. Baca hanya diperlukan untuk mengembalikan setidaknya 1 byte.
mafu
Saya akan memastikan untuk membungkus pemain lama dengan int dengan konstruksi yang diperiksa seperti ini: checked ((int) fs.Length)
tzup
Saya hanya akan melakukannya var binaryReader = new BinaryReader(fs); fileData = binaryReader.ReadBytes((int)fs.Length);dalam usingpernyataan itu. Tapi itu efektif seperti apa yang OP lakukan, hanya saya memotong baris kode dengan casting fs.Lengthuntuk intbukannya mendapatkan longnilai FileInfopanjang dan mengkonversi itu.
vapcguy
9

Bergantung pada frekuensi operasi, ukuran file, dan jumlah file yang Anda lihat, ada masalah kinerja lain yang perlu dipertimbangkan. Satu hal yang perlu diingat, adalah bahwa masing-masing byte array Anda akan dirilis pada belas kasihan pengumpul sampah. Jika Anda tidak melakukan caching data apa pun, Anda bisa menghasilkan banyak sampah dan kehilangan sebagian besar kinerja Anda menjadi % Waktu dalam GC. Jika potongan lebih besar dari 85K, Anda akan mengalokasikan ke Large Object Heap (LOH) yang akan membutuhkan koleksi semua generasi untuk dibebaskan (ini sangat mahal, dan pada server akan menghentikan semua eksekusi saat sedang berlangsung ). Selain itu, jika Anda memiliki banyak objek pada LOH, Anda dapat berakhir dengan fragmentasi LOH (LOH tidak pernah dipadatkan) yang mengarah pada kinerja yang buruk dan keluar dari memori. Anda dapat mendaur ulang proses setelah mencapai titik tertentu, tetapi saya tidak tahu apakah itu praktik terbaik.

Intinya adalah, Anda harus mempertimbangkan siklus hidup lengkap aplikasi Anda sebelum hanya membaca semua byte ke dalam memori secepat mungkin atau Anda mungkin memperdagangkan kinerja jangka pendek untuk kinerja keseluruhan.

Joel
sumber
source code C # tentang hal itu, untuk mengelola garbage collector, chunks, kinerja, acara counter , ...
PreguntonCojoneroCabrón
6

Saya katakan BinaryReaderbaik-baik saja, tetapi bisa dire-refored untuk ini, alih-alih semua baris kode untuk mendapatkan panjang buffer:

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

Seharusnya lebih baik daripada menggunakan .ReadAllBytes(), karena saya melihat di komentar pada respon teratas yang mencakup .ReadAllBytes()bahwa salah satu komentator memiliki masalah dengan file> 600 MB, karena a BinaryReaderdimaksudkan untuk hal semacam ini. Juga, memasukkannya ke dalam usingpernyataan memastikan FileStreamdan BinaryReaderditutup serta dibuang.

vapcguy
sumber
Untuk C #, perlu menggunakan "using (FileStream fs = File.OpenRead (fileName))" daripada "using (FileStream fs = File baru. OpenRead (fileName))" seperti yang diberikan di atas. Baru saja menghapus kata kunci baru sebelum File.OpenRead ()
Syed Mohamed
@Syed Kode di atas ditulis untuk C #, tetapi Anda benar bahwa newtidak diperlukan di sana. Dihapus.
vapcguy
1

Dalam hal 'file besar' dimaksudkan di luar batas 4GB, maka logika kode tertulis berikut sesuai. Masalah utama yang perlu diperhatikan adalah tipe data PANJANG yang digunakan dengan metode SEEK. Sebagai PANJANG mampu menunjukkan 2 ^ 32 batas data. Dalam contoh ini, kode memproses pertama memproses file besar dalam potongan 1GB, setelah seluruh potongan 1GB besar diproses, byte sisa (<1GB) diproses. Saya menggunakan kode ini dengan menghitung CRC file di luar ukuran 4GB. (menggunakan https://crc32c.machinezoo.com/ untuk perhitungan crc32c dalam contoh ini)

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;
}
Menno de Ruiter
sumber
0

Gunakan kelas BufferedStream di C # untuk meningkatkan kinerja. Buffer adalah blok byte dalam memori yang digunakan untuk menyimpan data, sehingga mengurangi jumlah panggilan ke sistem operasi. Buffer meningkatkan kinerja membaca dan menulis.

Lihat berikut ini untuk contoh kode dan penjelasan tambahan: http://msdn.microsoft.com/en-us/library/system.io.bufferedstream.aspx

Todd Moses
sumber
Apa gunanya menggunakan BufferedStreamketika Anda membaca semuanya sekaligus?
Mehrdad Afshari
Dia meminta kinerja terbaik untuk tidak membaca file sekaligus.
Todd Moses
9
Kinerja dapat diukur dalam konteks operasi. Penyangga tambahan untuk aliran yang Anda baca secara berurutan, sekaligus, ke memori sepertinya tidak akan mendapat manfaat dari penyangga tambahan.
Mehrdad Afshari
0

Gunakan ini:

 bytesRead = responseStream.ReadAsync(buffer, 0, Length).Result;
Disha Sharma
sumber
2
Selamat Datang di Stack Overflow! Karena penjelasan merupakan bagian penting dari jawaban pada platform ini, tolong jelaskan kode Anda dan bagaimana memecahkan masalah dalam pertanyaan dan mengapa itu mungkin lebih baik daripada jawaban lain. Panduan kami Cara menulis jawaban yang baik mungkin bermanfaat bagi Anda. Terima kasih
David
0

Tinjauan Umum: jika gambar Anda ditambahkan sebagai action = sumber daya yang disematkan maka gunakan GetExecutingAssembly untuk mengambil sumber daya jpg ke dalam aliran kemudian baca data biner dalam aliran ke dalam array byte

   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;

    }
Singa Emas
sumber
-4

Saya akan merekomendasikan mencoba Response.TransferFile()metode itu Response.Flush()dan Response.End()untuk melayani file-file besar Anda.

Dave
sumber
-7

Jika Anda berurusan dengan file di atas 2 GB, Anda akan menemukan bahwa metode di atas gagal.

Jauh lebih mudah untuk menyerahkan streaming ke MD5 dan membiarkannya memotong file Anda:

private byte[] computeFileHash(string filename)
{
    MD5 md5 = MD5.Create();
    using (FileStream fs = new FileStream(filename, FileMode.Open))
    {
        byte[] hash = md5.ComputeHash(fs);
        return hash;
    }
}
elaverick
sumber
11
Saya tidak melihat bagaimana kode ini relevan dengan pertanyaan (atau apa yang Anda sarankan dalam teks tertulis)
Vojtech B