Tworzenie tablicy bajtów ze strumienia

913

Jaka jest preferowana metoda tworzenia tablicy bajtów ze strumienia wejściowego?

Oto moje obecne rozwiązanie z .NET 3.5.

Stream s;
byte[] b;

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

Czy nadal lepiej jest czytać i pisać fragmenty strumienia?

Kok
źródło
60
Oczywiście, innym pytaniem jest, czy należy utworzyć bajt [] ze strumienia ... w przypadku dużych danych lepiej jest traktować strumień jako strumień.
Marc Gravell
2
Rzeczywiście powinieneś prawdopodobnie użyć strumienia zamiast bajtu []. Istnieją jednak systemowe interfejsy API, które nie obsługują strumieni. Na przykład nie możesz utworzyć X509Certificate2 ze strumienia, musisz nadać mu bajt [] (lub ciąg znaków). W tym przypadku jest w porządku, ponieważ certyfikat x509 prawdopodobnie nie jest dużymi danymi .
0xced

Odpowiedzi:

1293

To naprawdę zależy od tego, czy możesz zaufać s.Length. W przypadku wielu strumieni po prostu nie wiesz, ile będzie danych. W takich przypadkach - i wcześniej .NET 4 - użyłbym takiego kodu:

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

W .NET 4 i nowszych użyłbym Stream.CopyTo, co w zasadzie jest równoważne pętli w moim kodzie - utwórz MemoryStream, wywołaj, stream.CopyTo(ms)a następnie zwróć ms.ToArray(). Zadanie wykonane.

Być może powinienem wyjaśnić, dlaczego moja odpowiedź jest dłuższa niż inne. Stream.Readnie gwarantuje, że przeczyta wszystko, o co prosi. Jeśli na przykład czytasz ze strumienia sieciowego, może on odczytać wartość jednego pakietu, a następnie powrócić, nawet jeśli wkrótce będzie więcej danych. BinaryReader.Readbędzie działać do końca strumienia lub określonego rozmiaru, ale nadal musisz znać rozmiar, aby zacząć.

Powyższa metoda będzie odczytywać (i kopiować do a MemoryStream), dopóki nie zabraknie danych. Następnie prosi MemoryStreamo zwrócenie kopii danych w tablicy. Jeśli znasz rozmiar na początek - lub uważasz, że znasz rozmiar, bez pewności - możesz skonstruować MemoryStreamtaki rozmiar, aby zacząć. Podobnie możesz umieścić czek na końcu, a jeśli długość strumienia jest tego samego rozmiaru co bufor (zwracany przez MemoryStream.GetBuffer), możesz po prostu zwrócić bufor. Tak więc powyższy kod nie jest do końca zoptymalizowany, ale przynajmniej będzie poprawny. Nie ponosi żadnej odpowiedzialności za zamknięcie strumienia - dzwoniący powinien to zrobić.

Zobacz ten artykuł, aby uzyskać więcej informacji (i alternatywną implementację).

Jon Skeet
źródło
9
@Jon, warto wspomnieć o yoda.arachsys.com/csharp/readbinary.html
Sam Saffron
6
@Jeff: Tak naprawdę nie mamy tutaj kontekstu, ale jeśli piszesz do strumienia, to tak, musisz go „przewinąć” przed przeczytaniem. Jest tylko jeden „kursor” mówiący, gdzie jesteś w strumieniu - nie jeden do czytania i osobny do pisania.
Jon Skeet,
5
@Jeff: Obowiązkiem osoby dzwoniącej. W końcu strumień może nie być widoczny (np. Strumień sieciowy) lub może nie być po prostu potrzeby przewijania go do tyłu.
Jon Skeet
18
Czy mogę zapytać, dlaczego 16*1024konkretnie?
Anyname Donotcare
5
@ just_name: Nie wiem, czy to ma jakieś znaczenie, ale (16 * 1024) zdarza się być połową Int16.MaxValue :)
caesay
734

Chociaż odpowiedź Jona jest poprawna, przepisuje on kod, który już istnieje CopyTo. Tak więc dla .Net 4 użyj rozwiązania Sandip, ale dla poprzedniej wersji .Net użyj odpowiedzi Jona. Kod Sandip zostałby ulepszony poprzez użycie „using”, ponieważ w wyjątkowych sytuacjach wyjątki CopyTosą w wielu przypadkach dość prawdopodobne i pozostawiłby MemoryStreamniezbywalne.

public static byte[] ReadFully(Stream input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        input.CopyTo(ms);
        return ms.ToArray();
    }
}
Nathan Phillips
źródło
6
Jaka jest różnica między twoją odpowiedzią a odpowiedzią Jona? Również muszę zrobić to input.Position = 0, aby CopyTo działało.
Jeff
1
@nathan, przeczytaj plik z klienta WWW (filizesize = 1mb) - iis będzie musiał załadować cały 1mb do swojej pamięci, prawda?
Royi Namir
5
@Jeff, moja odpowiedź będzie działać tylko na .Net 4 lub nowszym, Jons będzie działał na niższych wersjach, przepisując funkcje udostępnione nam w późniejszej wersji. Masz rację, że CopyTo będzie kopiować tylko z bieżącej pozycji, jeśli masz strumień Seekable i chcesz skopiować od początku, możesz przejść do początku za pomocą kodu lub danych wejściowych. Szukaj (0, SeekOrigin.Begin), choć w wielu przypadkach Twój strumień może nie być widoczny.
Nathan Phillips
5
warto sprawdzić, czy inputjest już MemorySteamzwarty. Wiem, że głupio byłoby, gdyby osoba dzwoniąca zdała MemoryStreamale ...
Jodrell
3
@Jodrell, Dokładnie tak. Jeśli kopiujesz miliony małych strumieni do pamięci, a jednym z nich jest MemoryStreamto, czy optymalizacja ma sens w twoim kontekście, to porównanie czasu potrzebnego do wykonania milionów konwersji typów z czasem potrzebnym na skopiowanie tego, który jest MemoryStreamw inny MemoryStream.
Nathan Phillips
114

Chcę tylko zaznaczyć, że w przypadku, gdy masz MemoryStream, już to masz memorystream.ToArray().

Ponadto, jeśli masz do czynienia ze strumieniami nieznanych lub różnych podtypów i możesz otrzymać a MemoryStream, możesz polegać na tej metodzie w tych przypadkach i nadal używać przyjętej odpowiedzi dla innych, takich jak:

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
źródło
1
Huh, po co są wszystkie głosy poparcia? Nawet przy najbardziej hojnych założeniach działa to tylko w przypadku strumieni, które już są MemoryStream. Oczywiście przykład jest oczywiście niekompletny, ponieważ używa niezainicjowanej zmiennej.
Roman Starkov
3
Zgadza się, dziękuję za zwrócenie na to uwagi. Chodzi jednak o MemoryStream, więc naprawiłem to, aby to odzwierciedlić.
Fernando Neira,
Wystarczy wspomnieć, że dla MemoryStream inną możliwością jest MemoryStream.GetBuffer (), chociaż w grę wchodzą pewne problemy. Zobacz stackoverflow.com/questions/1646193/… i krishnabhargav.blogspot.dk/2009/06/…
RenniePet
4
To faktycznie wprowadza błąd w kodzie Skeeta; Jeśli zadzwonisz stream.Seek(1L, SeekOrigin.Begin), zanim zechcesz wywołać, jeśli strumień jest strumieniem pamięci, otrzymasz 1 bajt więcej niż w przypadku jakiegokolwiek innego strumienia. Jeśli dzwoniący spodziewa się odczytać z miejsca, w którym znajduje się bieżąca pozycja, do końca strumienia, nie wolno używać CopyTolub ToArray(); W większości przypadków nie będzie to stanowić problemu, ale jeśli dzwoniący nie będzie wiedział o tym dziwnym zachowaniu, będzie zdezorientowany.
leat
67
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();
Sandip Patel
źródło
9
MemoryStream należy utworzyć za pomocą „new MemoryStream (file.PostedFile.ContentLength)”, aby uniknąć fragmentacji pamięci.
Dan Randolph
52

tylko moja para centów ... praktyką, której często używam jest organizowanie takich metod jako niestandardowego pomocnika

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

dodaj przestrzeń nazw do pliku konfiguracyjnego i używaj go w dowolnym miejscu

Pan Dynia
źródło
5
Pamiętaj, że to nie będzie działać w .NET 3.5 i niższych CopyTowersjach, ponieważ nie było dostępne Streamdo 4.0.
Tim
15

Możesz po prostu użyć metody ToArray () klasy MemoryStream, np.

MemoryStream ms = (MemoryStream)dataInStream;
byte[] imageBytes = ms.ToArray();
Nilesh Kumar
źródło
10

Możesz nawet uczynić go bardziej eleganckim dzięki rozszerzeniom:

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

A następnie nazwij to jako zwykłą metodę:

byte[] arr = someStream.ToByteArray()
Michał T.
źródło
67
Myślę, że złym pomysłem jest umieszczenie strumienia wejściowego w bloku używającym. Odpowiedzialność ta powinna spoczywać na procedurze wywoływania.
Jeff
7

Dostaję błąd czasu kompilacji z kodem Boba (tj. Pytającego). Stream.Length jest długi, natomiast BinaryReader.ReadBytes przyjmuje parametr w postaci liczby całkowitej. W moim przypadku nie oczekuję, że będę mieć do czynienia ze strumieniami wystarczająco dużymi, aby wymagać długiej precyzji, dlatego używam następujących elementów:

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
źródło
5

Jeśli komuś się spodoba, oto rozwiązanie oparte wyłącznie na .NET 4+ utworzone jako metoda rozszerzenia bez niepotrzebnego wywołania Dispose w MemoryStream. Jest to beznadziejnie trywialna optymalizacja, ale warto zauważyć, że nieusunięcie MemoryStream nie jest prawdziwą porażką.

public static class StreamHelpers
{
    public static byte[] ReadFully(this Stream input)
    {
        var ms = new MemoryStream();
        input.CopyTo(ms);
        return ms.ToArray();
    }
}
SensorSmith
źródło
3

Powyższe jest w porządku ... ale podczas wysyłania danych przez SMTP (jeśli to konieczne) napotkasz uszkodzenie danych. Zmieniłem na coś innego, co pomoże poprawnie przesłać bajt po bajcie: '

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;
        }'
NothinRandom
źródło
Nie widzę, gdzie ten kod pozwala uniknąć uszkodzenia danych. Możesz to wyjaśnić?
Nippey,
Powiedzmy, że masz zdjęcie i chcesz je wysłać przez SMTP. Prawdopodobnie użyjesz kodowania base64. Z jakiegoś powodu plik ulega uszkodzeniu, jeśli zostanie rozbity na bajty. Jednak użycie czytnika binarnego pozwoli na pomyślne wysłanie pliku.
NothinRandom
3
Nieco stary, ale czułem, że niedźwiedzie wspominają - implementacja @NothinRandom zapewnia prace z ciągami, a nie strumieniami. Prawdopodobnie najłatwiej byłoby po prostu użyć File.ReadAllBytes w tym przypadku.
XwipeoutX
1
Głosuj z powodu niebezpiecznego stylu kodu (brak automatycznego usuwania / używania).
arni
Niestety tylko -1 dozwolone, nie ma nic wspólnego z pytaniem, parametrem nazwy pliku o nazwie input, nie usuwanym, bez bufora odczytu, bez trybu pliku i czytnika binarnego do odczytu bajt po bajcie, dlaczego?
Aridane Álamo
2

Utwórz klasę pomocnika i odwołaj się do niej w dowolnym miejscu, w którym chcesz jej użyć.

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

W przestrzeni nazw RestSharp.Extensions znajduje się metoda ReadAsBytes. Wewnątrz tej metody zastosowano MemoryStream i jest taki sam kod jak w niektórych przykładach na tej stronie, ale gdy używasz RestSharp, jest to najłatwiejszy sposób.

using RestSharp.Extensions;
var byteArray = inputStream.ReadAsBytes();
Wiesław Olborski
źródło
1

Możesz użyć tej metody rozszerzenia.

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
źródło
1

Jest to funkcja, której używam, przetestowałem i działałem dobrze. pamiętaj, że „wejście” nie powinno mieć wartości zerowej, a „wejście.pozycjonowanie” powinno zostać zresetowane do „0” przed odczytem, ​​w przeciwnym razie przerwie to pętlę odczytu i nic nie będzie czytać w celu konwersji na tablicę.

    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
źródło
-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
źródło
Właśnie skopiowałeś kod z odpowiedzi nr 1 i nr 3, nie dodając nic cennego. Proszę nie rób tego. :)
CodeCaster
Po dodaniu kodu opisz krótko proponowane rozwiązanie.
yakobom
-5

udało mi się sprawić, że będzie działać na jednej linii:

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

jak wyjaśnił johnnyRose , powyższy kod będzie działał tylko dla MemoryStream

Abba
źródło
2
Co jeśli localStreamnie jest MemoryStream? Ten kod zawiedzie.
johnnyRose
localStream musi być obiektem opartym na strumieniu. więcej na temat obiektu opartego na strumieniu tutaj stackoverflow.com/questions/8156896/…
Abba
1
Co starałem się zasugerować to, jeśli spróbujesz oddanych localStreamdo A MemoryStream, ale localStreamto nieMemoryStream , to będzie zawieść. Ten kod kompiluje się dobrze, ale może się nie powieść w czasie wykonywania, w zależności od faktycznego typu localStream. Nie zawsze możesz arbitralnie rzutować typ podstawowy na typ potomny; czytaj więcej tutaj . To kolejny dobry przykład, który wyjaśnia, dlaczego nie zawsze możesz to zrobić.
johnnyRose
W celu rozwinięcia mojego powyższego komentarza: wszystkie strumienie pamięci są strumieniami, ale nie wszystkie strumienie są strumieniami pamięci.
johnnyRose
wszystkie obiekty oparte na Stream mają Stream jako typ podstawowy. A sam strumień zawsze można przekształcić w strumień pamięci. Bez względu na to, jaki obiekt oparty na strumieniu próbujesz rzutować na Meomry Stream, zawsze powinien on działać. Naszym celem jest tutaj konwersja obiektu strumienia na tablicę bajtów. Czy możesz podać mi sygnaturę, w której to zawiedzie?
Abba,