Creazione di una matrice di byte da un flusso


913

Qual è il metodo preferito per creare un array di byte da un flusso di input?

Ecco la mia attuale soluzione con .NET 3.5.

Stream s;
byte[] b;

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

È ancora un'idea migliore leggere e scrivere blocchi dello stream?


60
Naturalmente, un'altra domanda è se dovessi creare un byte [] da uno stream ... per dati di grandi dimensioni, è preferibile trattare lo stream come pure uno stream!
Marc Gravell

2
In effetti, dovresti probabilmente usare uno stream anziché un byte []. Ma ci sono alcune API di sistema che non supportano gli stream. Ad esempio, non è possibile creare un certificato X509 da uno stream, è necessario assegnargli un byte [] (o una stringa). In questo caso va bene poiché un certificato x509 probabilmente non è un dato di grandi dimensioni .
forzato

Risposte:


1295

Dipende davvero dalla fiducia s.Length. Per molti stream, semplicemente non sai quanti dati ci saranno. In questi casi - e prima di .NET 4 - utilizzerei il codice in questo modo:

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

Con .NET 4 e versioni successive, utilizzerei Stream.CopyTo, che è sostanzialmente equivalente al ciclo nel mio codice: crea MemoryStream, chiama stream.CopyTo(ms)e poi ritorna ms.ToArray(). Lavoro fatto.

Dovrei forse spiegare perché la mia risposta è più lunga delle altre. Stream.Readnon garantisce che leggerà tutto ciò che viene richiesto. Se stai leggendo da un flusso di rete, ad esempio, potrebbe leggere il valore di un pacchetto e quindi restituirlo, anche se presto ci saranno più dati. BinaryReader.Readcontinuerà fino alla fine dello stream o alla dimensione specificata, ma devi comunque conoscere la dimensione con cui iniziare.

Il metodo sopra continuerà a leggere (e copiare in a MemoryStream) fino a quando non si esauriscono i dati. Quindi chiede MemoryStreamdi restituire una copia dei dati in un array. Se conosci le dimensioni con cui iniziare - o pensi di conoscerne le dimensioni, senza essere sicuro - puoi costruirle MemoryStreamper iniziare con quelle dimensioni. Allo stesso modo puoi mettere un segno di spunta alla fine e se la lunghezza del flusso ha le stesse dimensioni del buffer (restituito da MemoryStream.GetBuffer), puoi semplicemente restituire il buffer. Quindi il codice sopra non è del tutto ottimizzato, ma almeno sarà corretto. Non si assume alcuna responsabilità per la chiusura del flusso - il chiamante dovrebbe farlo.

Vedi questo articolo per maggiori informazioni (e un'implementazione alternativa).


9
@Jon, potrebbe valere la pena menzionare yoda.arachsys.com/csharp/readbinary.html
Sam Saffron,

6
@Jeff: Non abbiamo davvero il contesto qui, ma se hai scritto su uno stream, allora sì, devi "riavvolgerlo" prima di leggere. C'è solo un "cursore" che dice dove ti trovi all'interno dello stream, non uno per la lettura e uno separato per la scrittura.
Jon Skeet,

5
@Jeff: è responsabilità del chiamante. Dopotutto, il flusso potrebbe non essere ricercabile (ad esempio un flusso di rete) o potrebbe semplicemente non essere necessario riavvolgerlo.
Jon Skeet,

18
Potrei chiedere perché in 16*1024particolare?
Anyname Donotcare,

5
@just_name: non so se questo abbia qualche significato, ma (16 * 1024) sembra essere metà di Int16.MaxValue :)
ca

735

Mentre la risposta di Jon è corretta, sta riscrivendo il codice già esistente in CopyTo. Quindi per .Net 4 usa la soluzione di Sandip, ma per la versione precedente di .Net usa la risposta di Jon. Il codice di Sandip sarebbe migliorato usando l '"utilizzo" poiché le eccezioni CopyTosono, in molte situazioni, abbastanza probabili e lascerebbero il MemoryStreamnon disposto.

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

6
Che differenza fa tra la tua risposta e quella di Jon? Inoltre devo fare questo input.Position = 0 affinché il CopyTo funzioni.
Jeff

1
@nathan, leggi un file dal client web (filizesize = 1mb) - l'iis dovrà caricare l'intero 1mb nella sua memoria, giusto?
Royi Namir,

5
@Jeff, la mia risposta funzionerà solo su .Net 4 o versioni successive, Jons funzionerà su versioni inferiori riscrivendo la funzionalità fornita nella versione successiva. Hai ragione che CopyTo copierà solo dalla posizione corrente, se hai uno stream Cercabile e vuoi copiarlo dall'inizio, puoi spostarti all'inizio usando il tuo codice o input. Cerca (0, SeekOrigin.Begin), sebbene in molti casi il tuo stream potrebbe non essere ricercabile.
Nathan Phillips,

5
potrebbe valere la pena verificare se inputè già presente un MemorySteamcorto circuito. So che sarebbe stupido da parte del chiamante passare un MemoryStreamma ...
Jodrell,

3
@Jodrell, proprio così. Se stai copiando milioni di piccoli stream in memoria e uno di questi è un MemoryStreamse l'ottimizzazione ha senso nel tuo contesto è il confronto del tempo impiegato per fare milioni di conversioni di tipo rispetto al tempo impiegato per copiare quello che è MemoryStreamin un altro MemoryStream.
Nathan Phillips,

114

Voglio solo sottolineare che nel caso in cui possiedi un MemoryStream lo hai già fatto memorystream.ToArray().

Inoltre, se hai a che fare con flussi di sottotipi sconosciuti o diversi e puoi ricevere un MemoryStream, puoi inoltrare questo metodo per quei casi e usare ancora la risposta accettata per gli altri, in questo modo:

public static byte[] StreamToByteArray(Stream stream)
{
    if (stream is MemoryStream)
    {
        return ((MemoryStream)stream).ToArray();                
    }
    else
    {
        // Jon Skeet's accepted answer 
        return ReadFully(stream);
    }
}

1
Eh, a cosa servono tutti i voti? Anche con le ipotesi più generose, questo funziona solo per flussi che sono già MemoryStreams. Naturalmente anche l'esempio è ovviamente incompleto, nel modo in cui utilizza una variabile non inizializzata.
Roman Starkov,

3
Esatto, grazie per averlo sottolineato. Il punto sta ancora per MemoryStream, quindi l'ho risolto per riflettere questo.
Fernando Neira,

Basta menzionare che per MemoryStream un'altra possibilità è MemoryStream.GetBuffer (), sebbene siano coinvolti alcuni aspetti. Vedere stackoverflow.com/questions/1646193/... e krishnabhargav.blogspot.dk/2009/06/...
RenniePet

4
Questo in realtà introduce un bug nel codice di Skeet; Se chiamate stream.Seek(1L, SeekOrigin.Begin), prima di invocare prontamente, se il flusso è un flusso di memoria, otterrete 1 byte in più rispetto a qualsiasi altro flusso. Se il chiamante prevede di leggere da dove si trova la posizione corrente alla fine del flusso, non è necessario utilizzare CopyToo ToArray(); Nella maggior parte dei casi questo non sarà un problema, ma se il chiamante non è a conoscenza di questo comportamento bizzarro verranno confusi.
Leat

67
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();

9
MemoryStream dovrebbe essere creato con "new MemoryStream (file.PostedFile.ContentLength)" per evitare la frammentazione della memoria.
Dan Randolph,

52

solo i miei due centesimi ... la pratica che uso spesso è quella di organizzare i metodi come questo come un aiuto personalizzato

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

aggiungi lo spazio dei nomi al file di configurazione e usalo dove vuoi


5
Si noti che questo non funzionerà in .NET 3.5 e CopyToversioni precedenti poiché non era disponibile Streamfino alla 4.0.
Tim

16

Puoi semplicemente usare il metodo ToArray () della classe MemoryStream, per esempio

MemoryStream ms = (MemoryStream)dataInStream;
byte[] imageBytes = ms.ToArray();

10

Puoi persino renderlo più elaborato con le estensioni:

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

E poi chiamalo come metodo normale:

byte[] arr = someStream.ToByteArray()

67
Penso che sia una cattiva idea inserire lo stream di input in un blocco using. Tale responsabilità dovrebbe spettare alla procedura di chiamata.
Jeff,

7

Ottengo un errore di tempo di compilazione con il codice di Bob (ovvero il questionatore). Stream.Length è un lungo mentre BinaryReader.ReadBytes accetta un parametro intero. Nel mio caso, non mi aspetto di avere a che fare con stream abbastanza grandi da richiedere una lunga precisione, quindi utilizzo quanto segue:

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

5

Nel caso in cui piaccia a qualcuno, ecco una soluzione .NET 4+ formata come metodo di estensione senza la inutile chiamata Dispose su MemoryStream. Questa è un'ottimizzazione irrimediabilmente banale, ma vale la pena notare che non riuscire a smaltire un MemoryStream non è un vero fallimento.

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

3

Quello sopra è ok ... ma riscontrerai un danneggiamento dei dati quando invii materiale tramite SMTP (se necessario). Ho modificato qualcos'altro che aiuterà a inviare correttamente byte per byte: '

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

Non vedo dove questo codice eviti il ​​danneggiamento dei dati. Puoi spiegarlo?
Nippey,

Diciamo che hai una foto e vuoi inviarla via SMTP. Probabilmente utilizzerai la codifica base64. Per qualche motivo, il file viene danneggiato se lo si scompone in byte. Tuttavia, l'utilizzo di un lettore binario consentirà di inviare correttamente il file.
NothinRandom,

3
Un po 'vecchio, ma ho sentito che questo vale la pena menzionare: l'implementazione @NothinRandom fornisce lavori con stringhe, non flussi. Tuttavia, sarebbe probabilmente più semplice utilizzare File.ReadAllBytes in questo caso.
XwipeoutX,

1
Downvote a causa di uno stile di codice pericoloso (nessun smaltimento / utilizzo automatico).
arni,

Purtroppo solo -1 ha permesso, niente a che fare con la domanda, parametro nome file chiamato input, non disposing, nessun buffer di lettura, nessuna modalità file e lettore binario per leggere byte per byte perché?
Aridane Álamo,

2

Crea una classe di supporto e fai riferimento ovunque tu voglia usarla.

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

2

Nello spazio dei nomi RestSharp.Extensions è presente il metodo ReadAsBytes. All'interno di questo metodo viene utilizzato MemoryStream e c'è lo stesso codice come in alcuni esempi in questa pagina ma quando si utilizza RestSharp questo è il modo più semplice.

using RestSharp.Extensions;
var byteArray = inputStream.ReadAsBytes();

1

È possibile utilizzare questo metodo di estensione.

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

1

Questa è la funzione che sto usando, testato e ha funzionato bene. tenere presente che "input" non deve essere nullo e "input.position" deve reimpostare su "0" prima di leggere, altrimenti si interromperà il ciclo di lettura e nulla verrà letto per convertire in 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;
        }
    }

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

Hai appena copiato il codice dalla risposta n. 1 e n. 3 senza aggiungere nulla di prezioso. Per favore, non farlo. :)
CodeCaster

Quando aggiungi un codice, descrivi anche la soluzione proposta a breve.
yakobom,

-5

sono stato in grado di farlo funzionare su una sola riga:

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

come chiarito da johnnyRose , il codice di cui sopra funzionerà solo per MemoryStream


2
E se localStreamnon fosse un MemoryStream? Questo codice fallirà.
johnnyRose

localStream deve essere un oggetto basato su stream. ulteriori informazioni sull'oggetto basato su stream qui stackoverflow.com/questions/8156896/…
Abba

1
Quello che stavo cercando di suggerire è, se si tenta di fusione localStreamad una MemoryStream, ma localStreamè non è una MemoryStream, si avrà esito negativo. Questo codice verrà compilato correttamente, ma potrebbe non riuscire in fase di esecuzione, a seconda del tipo effettivo di localStream. Non è sempre possibile eseguire il cast arbitrario di un tipo di base su un tipo figlio; leggi di più qui . Questo è un altro buon esempio che spiega perché non puoi sempre farlo.
johnnyRose

Per approfondire il mio commento sopra: tutti i flussi di memoria sono flussi, ma non tutti i flussi sono flussi di memoria.
johnnyRose

tutti gli oggetti basati su Stream hanno Stream come tipo base. E lo stesso flusso può sempre essere convertibile in flusso di memoria. Indipendentemente dall'oggetto basato su stream che si tenta di trasmettere a Meomry Stream, dovrebbe sempre funzionare. Il nostro obiettivo qui è convertire l'oggetto stream in array di byte. Puoi darmi un caso significativo in cui fallirà?
Abba,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.