Membuat array byte dari aliran

913

Apa metode yang lebih disukai untuk membuat array byte dari aliran input?

Berikut adalah solusi saya saat ini dengan .NET 3.5.

Stream s;
byte[] b;

using (BinaryReader br = new BinaryReader(s))
{
    b = br.ReadBytes((int)s.Length);
}

Apakah masih lebih baik untuk membaca dan menulis potongan-potongan aliran?

Bob
sumber
60
Tentu saja, pertanyaan lain adalah apakah Anda membuat byte [] dari aliran ... untuk data besar, lebih baik memperlakukan aliran itu sebagai aliran!
Marc Gravell
2
Memang Anda mungkin harus menggunakan stream alih-alih byte []. Tetapi ada beberapa API sistem yang tidak mendukung stream. Misalnya, Anda tidak dapat membuat X509Certificate2 dari aliran, Anda harus memberikannya byte [] (atau string). Dalam hal ini tidak apa-apa karena sertifikat x509 mungkin bukan data yang besar .
0x

Jawaban:

1295

Itu benar-benar tergantung pada apakah Anda bisa percaya atau tidak s.Length. Untuk banyak aliran, Anda tidak tahu berapa banyak data yang akan ada. Dalam kasus seperti itu - dan sebelum .NET 4 - Saya akan menggunakan kode seperti ini:

public static byte[] ReadFully(Stream input)
{
    byte[] buffer = new byte[16*1024];
    using (MemoryStream ms = new MemoryStream())
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
            ms.Write(buffer, 0, read);
        }
        return ms.ToArray();
    }
}

Dengan .NET 4 dan di atasnya, saya akan menggunakan Stream.CopyTo, yang pada dasarnya setara dengan loop dalam kode saya - buat MemoryStream, panggil stream.CopyTo(ms)dan kembalilah ms.ToArray(). Pekerjaan selesai.

Saya mungkin harus menjelaskan mengapa jawaban saya lebih panjang dari yang lain. Stream.Readtidak menjamin bahwa itu akan membaca semua yang diminta. Jika Anda membaca dari aliran jaringan, misalnya, ia mungkin membaca nilai satu paket dan kemudian kembali, bahkan jika akan ada lebih banyak data segera. BinaryReader.Readakan terus berjalan hingga akhir streaming atau ukuran yang Anda tentukan, tetapi Anda masih harus tahu ukuran untuk memulai.

Metode di atas akan terus membaca (dan menyalin ke a MemoryStream) sampai kehabisan data. Kemudian meminta MemoryStreamuntuk mengembalikan salinan data dalam array. Jika Anda tahu ukuran untuk memulai - atau berpikir Anda tahu ukurannya, tanpa yakin - Anda bisa membuat MemoryStreammenjadi ukuran itu. Demikian juga Anda dapat memberi tanda centang di akhir, dan jika panjang aliran adalah ukuran yang sama dengan buffer (dikembalikan oleh MemoryStream.GetBuffer) maka Anda bisa mengembalikan buffer. Jadi kode di atas tidak cukup dioptimalkan, tetapi setidaknya akan benar. Itu tidak memikul tanggung jawab untuk menutup aliran - penelepon harus melakukan itu.

Lihat artikel ini untuk info lebih lanjut (dan implementasi alternatif).

Jon Skeet
sumber
9
@ Jon, mungkin perlu disebutkan yoda.arachsys.com/csharp/readbinary.html
Sam Saffron
6
@ Jeff: Kami tidak benar-benar memiliki konteks di sini, tetapi jika Anda telah menulis ke aliran, maka ya Anda perlu "mundur" sebelum membaca. Hanya ada satu "kursor" yang mengatakan di mana Anda berada dalam aliran - tidak satu untuk membaca dan yang terpisah untuk menulis.
Jon Skeet
5
@ Jeff: Ini adalah tanggung jawab penelepon. Bagaimanapun, aliran mungkin tidak dapat dicari (misalnya aliran jaringan) atau mungkin tidak perlu untuk memundurkannya.
Jon Skeet
18
Bisakah saya bertanya mengapa 16*1024secara spesifik?
Anyname Donotcare
5
@just_name: Saya tidak tahu apakah ini memiliki signifikansi, tetapi (16 * 1024) kebetulan setengah dari Int16.MaxValue :)
caesay
735

Sementara jawaban Jon benar, dia menulis ulang kode yang sudah ada di CopyTo. Jadi untuk .Net 4 gunakan solusi Sandip, tetapi untuk versi .Net sebelumnya gunakan jawaban Jon. Kode Sandip akan ditingkatkan dengan menggunakan "menggunakan" sebagai pengecualian dalam CopyTo, dalam banyak situasi, sangat mungkin dan akan meninggalkan yang MemoryStreamtidak dibuang.

public static byte[] ReadFully(Stream input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        input.CopyTo(ms);
        return ms.ToArray();
    }
}
Nathan Phillips
sumber
6
Apa bedanya antara jawaban Anda dan jawaban Jon? Saya juga harus melakukan input ini. Posisi = 0 agar CopyTo berfungsi.
Jeff
1
@nathan, baca file dari klien web (filizeize = 1mb) - iis harus memuat seluruh 1mb ke memorinya kan?
Royi Namir
5
@ Jeff, jawaban saya hanya akan bekerja pada. Net 4 atau lebih tinggi, Jons akan bekerja pada versi yang lebih rendah dengan menulis ulang fungsionalitas yang diberikan kepada kami di versi yang lebih baru. Anda benar bahwa CopyTo hanya akan menyalin dari posisi saat ini, jika Anda memiliki aliran Seekable dan Anda ingin menyalin dari awal maka Anda dapat pindah ke awal menggunakan kode atau input Anda. Carilah (0, SeekOrigin.Begin), meskipun dalam banyak kasus aliran Anda mungkin tidak dapat dicari.
Nathan Phillips
5
mungkin perlu memeriksa apakah inputsudah terjadi MemorySteamdan korsleting. Saya tahu itu akan bodoh dari penelepon untuk lulus MemoryStreamtetapi ...
Jodrell
3
@Jodrell, Tepat sekali. Jika Anda menyalin jutaan aliran kecil ke dalam memori dan salah satunya adalah MemoryStreammaka apakah pengoptimalan tersebut masuk akal dalam konteks Anda adalah perbandingan waktu yang diperlukan untuk melakukan jutaan konversi jenis dengan waktu yang diperlukan untuk menyalin aliran yang menjadi MemoryStreamsasaran lain MemoryStream.
Nathan Phillips
114

Hanya ingin menunjukkan bahwa seandainya Anda memiliki MemoryStream yang sudah Anda miliki memorystream.ToArray() untuk itu.

Juga, jika Anda berurusan dengan aliran subtipe yang tidak diketahui atau berbeda dan Anda dapat menerima MemoryStream, Anda dapat menyampaikan pada metode tersebut untuk kasus tersebut dan masih menggunakan jawaban yang diterima untuk yang lain, seperti ini:

public static byte[] StreamToByteArray(Stream stream)
{
    if (stream is MemoryStream)
    {
        return ((MemoryStream)stream).ToArray();                
    }
    else
    {
        // Jon Skeet's accepted answer 
        return ReadFully(stream);
    }
}
Fernando Neira
sumber
1
Huh, untuk apa semua upvotes? Bahkan dengan asumsi paling dermawan, ini hanya berfungsi untuk stream yang sudah MemoryStreams. Tentu saja contohnya juga jelas tidak lengkap, dalam cara menggunakan variabel yang tidak diinisialisasi.
Roman Starkov
3
Itu benar, terima kasih sudah menunjukkannya. Intinya masih singkatan MemoryStream, jadi saya memperbaikinya untuk mencerminkan itu.
Fernando Neira
Sebut saja bahwa untuk MemoryStream kemungkinan lain adalah MemoryStream.GetBuffer (), meskipun ada beberapa gotcha yang terlibat. Lihat stackoverflow.com/questions/1646193/… dan krishnabhargav.blogspot.dk/2009/06/…
RenniePet
4
Ini sebenarnya memperkenalkan bug ke dalam kode Skeet; Jika Anda menelepon stream.Seek(1L, SeekOrigin.Begin), sebelum Anda memanggilnya dengan mudah, jika alirannya adalah aliran memori, Anda akan mendapatkan 1 byte lebih banyak daripada jika itu adalah aliran lainnya. Jika penelepon mengharapkan untuk membaca dari mana posisi saat ini ke akhir aliran maka Anda tidak boleh menggunakan CopyToatau ToArray(); Dalam kebanyakan kasus ini tidak akan menjadi masalah, tetapi jika penelepon tidak tahu tentang perilaku aneh ini mereka akan bingung.
leat
67
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();
Sandip Patel
sumber
9
MemoryStream harus dibuat dengan "MemoryStream baru (file.PostedFile.ContentLength)" untuk menghindari fragmentasi memori.
Dan Randolph
52

hanya pasangan saya sen ... praktik yang sering saya gunakan adalah mengatur metode seperti ini sebagai pembantu kustom

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            input.CopyTo(ms);
            return ms.ToArray();
        }
    }
}

tambahkan namespace ke file konfigurasi dan gunakan di mana saja Anda inginkan

Tuan Pumpkin
sumber
5
Perhatikan bahwa ini tidak akan berfungsi di .NET 3.5 dan di bawah karena CopyTotidak tersedia Streamhingga 4.0.
Tim
16

Anda cukup menggunakan metode ToArray () dari kelas MemoryStream, untuk contoh

MemoryStream ms = (MemoryStream)dataInStream;
byte[] imageBytes = ms.ToArray();
Nilesh Kumar
sumber
10

Anda bahkan dapat membuatnya lebih mewah dengan ekstensi:

namespace Foo
{
    public static class Extensions
    {
        public static byte[] ToByteArray(this Stream stream)
        {
            using (stream)
            {
                using (MemoryStream memStream = new MemoryStream())
                {
                     stream.CopyTo(memStream);
                     return memStream.ToArray();
                }
            }
        }
    }
}

Dan kemudian menyebutnya sebagai metode biasa:

byte[] arr = someStream.ToByteArray()
Michal T
sumber
67
Saya pikir itu ide yang buruk untuk menempatkan input stream di blok menggunakan. Tanggung jawab itu harus berada pada prosedur pemanggilan.
Jeff
7

Saya mendapatkan kesalahan waktu kompilasi dengan kode Bob (yaitu si penanya). Stream.Length adalah panjang sedangkan BinaryReader.ReadBytes mengambil parameter integer. Dalam kasus saya, saya tidak berharap berurusan dengan Streaming yang cukup besar sehingga membutuhkan ketelitian yang lama, jadi saya menggunakan yang berikut ini:

Stream s;
byte[] b;

if (s.Length > int.MaxValue) {
  throw new Exception("This stream is larger than the conversion algorithm can currently handle.");
}

using (var br = new BinaryReader(s)) {
  b = br.ReadBytes((int)s.Length);
}
Brian Hinchey
sumber
5

Jika ada yang suka, ini adalah solusi .NET 4+ saja yang dibentuk sebagai metode ekstensi tanpa perlu Buang panggilan di MemoryStream. Ini adalah optimasi sepele yang sia-sia, tetapi perlu dicatat bahwa gagal untuk Membuang MemoryStream bukanlah kegagalan nyata.

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        var ms = new MemoryStream();
        input.CopyTo(ms);
        return ms.ToArray();
    }
}
SensorSmith
sumber
3

Yang di atas tidak apa-apa ... tetapi Anda akan menemukan korupsi data ketika Anda mengirim barang melalui SMTP (jika perlu). Saya telah mengubah ke hal lain yang akan membantu mengirimkan byte ke byte dengan benar: '

using System;
using System.IO;

        private static byte[] ReadFully(string input)
        {
            FileStream sourceFile = new FileStream(input, FileMode.Open); //Open streamer
            BinaryReader binReader = new BinaryReader(sourceFile);
            byte[] output = new byte[sourceFile.Length]; //create byte array of size file
            for (long i = 0; i < sourceFile.Length; i++)
                output[i] = binReader.ReadByte(); //read until done
            sourceFile.Close(); //dispose streamer
            binReader.Close(); //dispose reader
            return output;
        }'
Tidak ada yang acak
sumber
Saya tidak melihat di mana kode ini menghindari kerusakan data. Bisakah Anda menjelaskannya?
Nippey
Katakanlah Anda memiliki gambar dan Anda ingin mengirimnya melalui SMTP. Anda mungkin akan menggunakan pengkodean base64. Untuk beberapa alasan, file menjadi rusak jika Anda memecahnya sebagai byte. Namun, menggunakan pembaca biner akan memungkinkan file berhasil dikirim.
NothinRandom
3
Agak tua, tapi saya merasa ini menyebutkan - implementasi @NothinRandom menyediakan karya dengan string, bukan stream. Mungkin akan paling sederhana untuk menggunakan File.ReadAllBytes dalam kasus ini.
XwipeoutX
1
Downvote karena gaya kode berbahaya (tidak ada Buang / gunakan otomatis).
arni
Sayangnya hanya -1 yang diizinkan, tidak ada hubungannya dengan pertanyaan, nama file parameter bernama input, tidak membuang, tidak ada buffer membaca, tidak ada filemode, dan pembaca biner untuk membaca byte demi byte mengapa?
Aridane Álamo
2

Buat kelas pembantu dan rujuk di mana saja Anda ingin menggunakannya.

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        using (MemoryStream ms = new MemoryStream())
        {
            input.CopyTo(ms);
            return ms.ToArray();
        }
    }
}
Kalyn Padayachee
sumber
2

Di namespace RestSharp.Extensions ada metode ReadAsBytes. Di dalam metode ini digunakan MemoryStream dan ada kode yang sama seperti pada beberapa contoh di halaman ini tetapi ketika Anda menggunakan RestSharp ini adalah cara termudah.

using RestSharp.Extensions;
var byteArray = inputStream.ReadAsBytes();
Wieslaw Olborski
sumber
1

Anda dapat menggunakan metode ekstensi ini.

public static class StreamExtensions
{
    public static byte[] ToByteArray(this Stream stream)
    {
        var bytes = new List<byte>();

        int b;
        while ((b = stream.ReadByte()) != -1)
            bytes.Add((byte)b);

        return bytes.ToArray();
    }
}
Tempeck
sumber
1

Ini adalah fungsi yang saya gunakan, diuji dan bekerja dengan baik. harap diingat bahwa 'input' tidak boleh nol dan 'input.position' harus direset ke '0' sebelum membaca jika tidak maka akan merusak loop baca dan tidak ada yang akan membaca untuk mengkonversi ke array.

    public static byte[] StreamToByteArray(Stream input)
    {
        if (input == null)
            return null;
        byte[] buffer = new byte[16 * 1024];
        input.Position = 0;
        using (MemoryStream ms = new MemoryStream())
        {
            int read;
            while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                ms.Write(buffer, 0, read);
            }
            byte[] temp = ms.ToArray();

            return temp;
        }
    }
Fred
sumber
-1
public static byte[] ToByteArray(Stream stream)
    {
        if (stream is MemoryStream)
        {
            return ((MemoryStream)stream).ToArray();
        }
        else
        {
            byte[] buffer = new byte[16 * 1024];
            using (MemoryStream ms = new MemoryStream())
            {
                int read;
                while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }            
    }
atau lebih
sumber
Anda baru saja menyalin kode dari jawaban # 1 dan # 3 tanpa menambahkan sesuatu yang berharga. Tolong jangan lakukan itu. :)
CodeCaster
Saat Anda menambahkan kode, jelaskan juga solusi yang Anda usulkan segera.
yakobom
-5

saya dapat membuatnya bekerja pada satu baris:

byte [] byteArr= ((MemoryStream)localStream).ToArray();

seperti yang diklarifikasi oleh johnnyRose , kode di atas hanya akan berfungsi untuk MemoryStream

Abba
sumber
2
Bagaimana jika localStreambukan MemoryStream? Kode ini akan gagal.
johnnyRose
localStream harus menjadi objek berbasis aliran. lebih lanjut tentang objek berbasis aliran di sini stackoverflow.com/questions/8156896/...
Abba
1
Apa yang saya mencoba untuk menyarankan, jika Anda mencoba untuk cor localStreamuntuk MemoryStream, tetapi localStreamadalah tidak satu MemoryStream, itu akan gagal. Kode ini akan dikompilasi dengan baik, tetapi bisa gagal saat runtime, tergantung pada tipe aktual localStream. Anda tidak selalu bisa secara sewenang-wenang melemparkan tipe dasar ke tipe anak; baca lebih lanjut di sini . Ini adalah contoh bagus lainnya yang menjelaskan mengapa Anda tidak selalu bisa melakukan ini.
johnnyRose
Untuk menguraikan komentar saya di atas: semua MemoryStreams adalah Streaming, tetapi tidak semua Streaming adalah MemoryStreams.
johnnyRose
semua objek berbasis aliran memiliki aliran sebagai tipe dasar. Dan Stream itu sendiri selalu dapat dikonversi ke aliran memori. Tidak peduli apa pun objek berbasis aliran yang Anda coba untuk melemparkan ke Meomry Stream, itu harus selalu berfungsi. Tujuan kami di sini adalah untuk mengkonversi objek stream ke array byte. Bisakah Anda memberi saya kasus signle di mana itu akan gagal?
Abba