Erstellen eines Byte-Arrays aus einem Stream

913

Was ist die bevorzugte Methode zum Erstellen eines Byte-Arrays aus einem Eingabestream?

Hier ist meine aktuelle Lösung mit .NET 3.5.

Stream s;
byte[] b;

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

Ist es immer noch eine bessere Idee, Teile des Streams zu lesen und zu schreiben?

Bob
quelle
60
Eine andere Frage ist natürlich, ob Sie ein Byte [] aus einem Stream erstellen sollten. Bei großen Datenmengen ist es vorzuziehen, den Stream auch als Stream zu behandeln.
Marc Gravell
2
In der Tat sollten Sie wahrscheinlich einen Stream anstelle eines Bytes [] verwenden. Es gibt jedoch einige System-APIs, die keine Streams unterstützen. Sie können beispielsweise kein X509Certificate2 aus einem Stream erstellen, sondern müssen ihm ein Byte [] (oder eine Zeichenfolge) geben. In diesem Fall ist es in Ordnung, da ein x509-Zertifikat wahrscheinlich keine großen Datenmengen enthält .
0xced

Antworten:

1294

Es hängt wirklich davon ab, ob Sie vertrauen können oder nicht s.Length. Bei vielen Streams wissen Sie einfach nicht, wie viele Daten vorhanden sein werden. In solchen Fällen - und vor .NET 4 - würde ich folgenden Code verwenden:

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

Mit .NET 4 und höher würde ich verwenden Stream.CopyTo, was im Grunde der Schleife in meinem Code entspricht - erstellen Sie den MemoryStream, rufen Sie auf stream.CopyTo(ms)und kehren Sie dann zurück ms.ToArray(). Job erledigt.

Ich sollte vielleicht erklären, warum meine Antwort länger ist als die der anderen. Stream.Readgarantiert nicht, dass es alles liest, wonach es gefragt wird. Wenn Sie beispielsweise aus einem Netzwerk-Stream lesen, liest dieser möglicherweise den Wert eines Pakets und kehrt dann zurück, selbst wenn bald weitere Daten vorliegen. BinaryReader.Readwird bis zum Ende des Streams oder Ihrer angegebenen Größe fortgesetzt, aber Sie müssen zunächst noch die Größe kennen.

Bei der obigen Methode wird so lange gelesen (und in a kopiert MemoryStream), bis keine Daten mehr vorhanden sind. Anschließend wird der Benutzer MemoryStreamaufgefordert, eine Kopie der Daten in einem Array zurückzugeben. Wenn Sie zunächst die Größe kennen - oder glauben , die Größe zu kennen, ohne sich sicher zu sein -, können Sie die Größe zunächst so konstruieren MemoryStream, dass sie diese Größe hat. Ebenso können Sie am Ende ein Häkchen setzen. Wenn die Länge des Streams der Größe des Puffers entspricht (zurückgegeben von MemoryStream.GetBuffer), können Sie den Puffer einfach zurückgeben. Der obige Code ist also nicht ganz optimiert, wird aber zumindest korrekt sein. Es übernimmt keine Verantwortung für das Schließen des Streams - der Anrufer sollte dies tun.

Weitere Informationen (und eine alternative Implementierung) finden Sie in diesem Artikel .

Jon Skeet
quelle
9
@ Jon, es kann erwähnenswert sein, yoda.arachsys.com/csharp/readbinary.html
Sam Saffron
6
@ Jeff: Wir haben hier nicht wirklich den Kontext, aber wenn Sie in einen Stream geschrieben haben, müssen Sie ihn vor dem Lesen "zurückspulen". Es gibt nur einen "Cursor", der angibt, wo Sie sich im Stream befinden - keinen zum Lesen und einen separaten zum Schreiben.
Jon Skeet
5
@ Jeff: Es liegt in der Verantwortung des Anrufers. Schließlich ist der Stream möglicherweise nicht durchsuchbar (z. B. ein Netzwerk-Stream), oder es besteht einfach keine Notwendigkeit, ihn zurückzuspulen.
Jon Skeet
18
Könnte ich fragen warum 16*1024speziell?
Anyname Donotcare
5
@just_name: Ich weiß nicht, ob dies irgendeine Bedeutung hat, aber (16 * 1024) ist zufällig die Hälfte von Int16.MaxValue :)
Caesay
734

Während Jons Antwort richtig ist, schreibt er Code neu, der bereits in vorhanden ist CopyTo. Verwenden Sie für .Net 4 die Sandip-Lösung, für die vorherige Version von .Net jedoch Jons Antwort. Sandips Code würde durch die Verwendung von "using" verbessert, da Ausnahmen CopyToin vielen Situationen sehr wahrscheinlich sind und das MemoryStreamnicht entsorgt werden würden.

public static byte[] ReadFully(Stream input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        input.CopyTo(ms);
        return ms.ToArray();
    }
}
Nathan Phillips
quelle
6
Was unterscheidet es zwischen deiner Antwort und der von Jon? Außerdem muss ich diese Eingabe machen. Position = 0, damit CopyTo funktioniert.
Jeff
1
@nathan, lese eine Datei vom Web-Client (filizesize = 1mb) - das iis muss die gesamte 1mb in seinen Speicher laden, oder?
Royi Namir
5
@ Jeff, meine Antwort funktioniert nur in .NET 4 oder höher. Jons funktioniert in niedrigeren Versionen, indem die in der späteren Version bereitgestellten Funktionen neu geschrieben werden. Sie haben Recht, dass CopyTo nur von der aktuellen Position kopiert. Wenn Sie einen suchbaren Stream haben und von Anfang an kopieren möchten, können Sie mit Ihrem Code oder Ihrer Eingabe zum Anfang wechseln. Suchen (0, SeekOrigin.Begin), In vielen Fällen ist Ihr Stream jedoch möglicherweise nicht suchbar.
Nathan Phillips
5
Es könnte sich lohnen zu prüfen, ob inputbereits ein MemorySteamKurzschluss vorliegt. Ich weiß, es wäre dumm von dem Anrufer, ein MemoryStreamaber ...
Jodrell
3
@ Jodrell, genau so. Wenn Sie Millionen von kleinen Strömen in dem Speicher zu kopieren und einer von ihnen ist ein MemoryStreamdann , ob die Optimierung Sinn in Ihrem Kontext macht , ist der Vergleich der Zeit, um Millionen von Typkonvertierungen gegen die Zeit zu tun genommen , die eine kopieren , die eine ist MemoryStreamin ein anderer MemoryStream.
Nathan Phillips
114

Ich möchte nur darauf hinweisen, dass Sie für den Fall, dass Sie einen MemoryStream haben, bereits einen haben memorystream.ToArray().

Wenn Sie es mit Streams unbekannter oder unterschiedlicher Subtypen zu tun haben und eine erhalten können MemoryStream, können Sie diese Methode für diese Fälle weiterleiten und die akzeptierte Antwort für die anderen weiterhin wie folgt verwenden:

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
quelle
1
Huh, wofür sind all die Upvotes? Selbst mit den großzügigsten Annahmen funktioniert dies nur für Streams, die bereits MemoryStreams sind. Natürlich ist das Beispiel auch offensichtlich unvollständig, da es eine nicht initialisierte Variable verwendet.
Roman
3
Das ist richtig, danke, dass Sie darauf hingewiesen haben. Der Punkt steht jedoch immer noch für MemoryStream, daher habe ich ihn korrigiert, um dies widerzuspiegeln.
Fernando Neira
Erwähnen Sie einfach, dass für MemoryStream eine andere Möglichkeit MemoryStream.GetBuffer () ist, obwohl einige Fallstricke involviert sind. Siehe stackoverflow.com/questions/1646193/… und krishnabhargav.blogspot.dk/2009/06/…
RenniePet
4
Dies führt tatsächlich einen Fehler in Skeets Code ein. Wenn Sie aufrufen stream.Seek(1L, SeekOrigin.Begin), bevor Sie lesbar aufrufen , erhalten Sie, wenn der Stream ein Speicher-Stream ist, 1 Byte mehr als wenn es sich um einen anderen Stream handelt. Wenn der Anrufer erwartet, von der aktuellen Position bis zum Ende des Streams zu lesen, dürfen Sie CopyTooder nicht verwenden ToArray(). In den meisten Fällen ist dies kein Problem, aber wenn der Anrufer nichts über dieses eigenartige Verhalten weiß, werden sie verwirrt.
Leat
67
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();
Sandip Patel
quelle
9
MemoryStream sollte mit "new MemoryStream (file.PostedFile.ContentLength)" erstellt werden, um eine Speicherfragmentierung zu vermeiden.
Dan Randolph
52

nur meine paar Cent ... die Praxis, die ich oft benutze, besteht darin, die Methoden wie diese als benutzerdefinierten Helfer zu organisieren

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

Fügen Sie der Konfigurationsdatei einen Namespace hinzu und verwenden Sie ihn an einer beliebigen Stelle

Mr. Pumpkin
quelle
5
Beachten Sie, dass dies in .NET 3.5 und darunter nicht funktioniert, da CopyToes Streamerst ab 4.0 verfügbar war.
Tim
16

Sie können einfach die ToArray () -Methode der MemoryStream-Klasse verwenden, z.

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

Mit Erweiterungen können Sie es sogar noch schicker machen:

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

Und nennen Sie es dann als reguläre Methode:

byte[] arr = someStream.ToByteArray()
Michal T.
quelle
67
Ich denke, es ist eine schlechte Idee, den Eingabestream in einen using-Block zu setzen. Diese Verantwortung sollte beim Aufrufverfahren liegen.
Jeff
7

Ich erhalte einen Fehler bei der Kompilierung mit dem Code von Bob (dh dem des Fragestellers). Stream.Length ist lang, während BinaryReader.ReadBytes einen ganzzahligen Parameter verwendet. In meinem Fall erwarte ich nicht, dass es sich um Streams handelt, die groß genug sind, um eine lange Genauigkeit zu erfordern. Daher verwende ich Folgendes:

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
quelle
5

Falls es jemandem gefällt, finden Sie hier eine .NET 4+ -Lösung, die als Erweiterungsmethode ohne den unnötigen Dispose-Aufruf im MemoryStream erstellt wurde. Dies ist eine hoffnungslos triviale Optimierung, aber es ist erwähnenswert, dass das Versäumnis, einen MemoryStream zu entsorgen, kein wirklicher Fehler ist.

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

Das obige ist in Ordnung ... aber Sie werden auf Datenbeschädigung stoßen, wenn Sie Inhalte über SMTP senden (falls erforderlich). Ich habe etwas anderes geändert, das hilft, Byte für Byte korrekt zu senden: '

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;
        }'
Nichts Zufälliges
quelle
Ich sehe nicht, wo dieser Code Datenbeschädigung vermeidet. Kannst du es erklären?
Nippey
Angenommen, Sie haben ein Bild und möchten es über SMTP senden. Sie werden wahrscheinlich die base64-Codierung verwenden. Aus irgendeinem Grund wird die Datei beschädigt, wenn Sie sie in Bytes aufteilen. Bei Verwendung eines Binärlesegeräts kann die Datei jedoch erfolgreich gesendet werden.
NothinRandom
3
Etwas alt, aber ich fand, dass dies erwähnenswert ist - die Implementierung @NothinRandom bietet Arbeiten mit Zeichenfolgen, nicht mit Streams. In diesem Fall wäre es wahrscheinlich am einfachsten, nur File.ReadAllBytes zu verwenden.
XwipeoutX
1
Downvote wegen gefährlichen Codestils (keine automatische Entsorgung / Verwendung).
Arni
Leider nur -1 erlaubt, nichts mit der Frage zu tun, Dateiname Parameter namens Eingabe, nicht verfügbar, kein Lesepuffer, kein Dateimodus und binärer Leser, um Byte für Byte zu lesen, warum?
Aridane Álamo
2

Erstellen Sie eine Hilfsklasse und verweisen Sie darauf, wo immer Sie sie verwenden möchten.

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

Im Namespace RestSharp.Extensions gibt es die Methode ReadAsBytes. In dieser Methode wird MemoryStream verwendet, und es gibt denselben Code wie in einigen Beispielen auf dieser Seite. Wenn Sie jedoch RestSharp verwenden, ist dies der einfachste Weg.

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

Sie können diese Erweiterungsmethode verwenden.

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
quelle
1

Dies ist die Funktion, die ich benutze, getestet und gut funktioniert habe. Bitte beachten Sie, dass 'input' nicht null sein sollte und 'input.position' vor dem Lesen auf '0' zurückgesetzt werden sollte, da sonst die Leseschleife unterbrochen wird und nichts gelesen wird, um in ein Array konvertiert zu werden.

    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.S
quelle
-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();
            }
        }            
    }
önder çalbay
quelle
Sie haben gerade den Code aus der Antwort Nr. 1 und Nr. 3 kopiert, ohne etwas Wertvolles hinzuzufügen. Bitte tu das nicht. :)
CodeCaster
Wenn Sie einen Code hinzufügen, beschreiben Sie auch kurz Ihre vorgeschlagene Lösung.
Yakobom
-5

Ich konnte es in einer einzigen Zeile zum Laufen bringen:

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

Wie von johnnyRose klargestellt, funktioniert der obige Code nur für MemoryStream

Abba
quelle
2
Was ist, wenn localStreamnicht MemoryStream? Dieser Code schlägt fehl.
Johnny Rose
localStream muss ein Stream-basiertes Objekt sein. Mehr über Stream-basierte Objekte hier stackoverflow.com/questions/8156896/…
Abba
1
Was ich versuche zu vorschlagen, wenn Sie versuchen , Guss localStreamzu ein MemoryStream, aber localStreamist nicht ein MemoryStream, es wird scheitern. Dieser Code wird einwandfrei kompiliert, kann jedoch zur Laufzeit fehlschlagen, abhängig vom tatsächlichen Typ von localStream. Sie können einen Basistyp nicht immer willkürlich in einen untergeordneten Typ umwandeln. Lesen Sie hier mehr . Dies ist ein weiteres gutes Beispiel, das erklärt, warum Sie dies nicht immer tun können.
Johnny Rose
Um auf meinen obigen Kommentar einzugehen: Alle MemoryStreams sind Streams, aber nicht alle Streams sind MemoryStreams.
Johnny Rose
Alle Stream-basierten Objekte haben Stream als Basistyp. Und Stream selbst kann immer in Speicher-Stream konvertiert werden. Unabhängig davon, welches Stream-basierte Objekt Sie in Meomry Stream umwandeln möchten, sollte es immer funktionieren. Unser Ziel hier ist es, das Stream-Objekt in ein Byte-Array umzuwandeln. Können Sie mir einen Unterschriftenfall geben, bei dem es fehlschlagen wird?
Abba