Criando uma matriz de bytes a partir de um fluxo

913

Qual é o método preferido para criar uma matriz de bytes a partir de um fluxo de entrada?

Aqui está minha solução atual com o .NET 3.5.

Stream s;
byte[] b;

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

Ainda é uma idéia melhor ler e gravar trechos do fluxo?

Prumo
fonte
60
Obviamente, outra pergunta é se você deve criar um byte [] a partir de um fluxo ... para dados grandes, é preferível tratar o fluxo como também um fluxo!
Marc Gravell
2
Na verdade, você provavelmente deve usar um fluxo em vez de um byte []. Mas existem algumas APIs de sistema que não suportam fluxos. Por exemplo, você não pode criar um X509Certificate2 a partir de um fluxo, é necessário atribuir a ele um byte [] (ou uma string). Nesse caso, tudo bem, pois um certificado x509 provavelmente não é um dado grande .
0xced

Respostas:

1295

Realmente depende se você pode ou não confiar s.Length. Para muitos fluxos, você simplesmente não sabe quantos dados haverá. Nesses casos - e antes do .NET 4 - eu usaria código como este:

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

Com o .NET 4 e superior, eu usaria Stream.CopyTo, que é basicamente equivalente ao loop no meu código - crie a MemoryStreamchamada, stream.CopyTo(ms)e depois retorne ms.ToArray(). Tarefa concluída.

Talvez eu devesse explicar por que minha resposta é mais longa que as outras. Stream.Readnão garante que ele leia tudo o que for solicitado. Se você estiver lendo de um fluxo de rede, por exemplo, ele poderá ler o valor de um pacote e retornar, mesmo que em breve haja mais dados. BinaryReader.Readcontinuará até o final do fluxo ou o tamanho especificado, mas você ainda precisa saber o tamanho para começar.

O método acima continuará lendo (e copiando em a MemoryStream) até ficar sem dados. Em seguida, solicita MemoryStreamque você retorne uma cópia dos dados em uma matriz. Se você sabe o tamanho para começar - ou acha que sabe o tamanho, sem ter certeza -, você pode MemoryStreamcriar o tamanho que seja para começar. Da mesma forma, você pode marcar no final e, se o comprimento do fluxo for do mesmo tamanho do buffer (retornado por MemoryStream.GetBuffer), basta retornar o buffer. Portanto, o código acima não é totalmente otimizado, mas pelo menos estará correto. Ele não assume nenhuma responsabilidade por fechar o fluxo - o chamador deve fazer isso.

Consulte este artigo para obter mais informações (e uma implementação alternativa).

Jon Skeet
fonte
9
@ Jon, pode valer a pena mencionar yoda.arachsys.com/csharp/readbinary.html
Sam Saffron
6
@ Jeff: Nós realmente não temos o contexto aqui, mas se você estiver escrevendo em um fluxo, sim, você precisará "retroceder" antes de ler. Há apenas um "cursor" dizendo onde você está no fluxo - não um para ler e outro para escrever.
precisa
5
@ Jeff: É de responsabilidade do interlocutor. Afinal, o fluxo pode não ser procurável (por exemplo, um fluxo de rede) ou pode simplesmente não haver necessidade de retroceder.
precisa
18
Eu poderia perguntar por que 16*1024especificamente?
Anyname Donotcare
5
@just_name: Eu não sei se isso tem algum significado, mas (16 * 1024) passa a ser metade do Int16.MaxValue :)
caesay
735

Enquanto a resposta de Jon está correta, ele está reescrevendo o código que já existe CopyTo. Portanto, para o .Net 4, use a solução de Sandip, mas para a versão anterior do .Net, use a resposta de Jon. O código de Sandip seria aprimorado pelo uso de "using", pois as exceções CopyTosão, em muitas situações, bastante prováveis ​​e deixariam os MemoryStreamnão descartados.

public static byte[] ReadFully(Stream input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        input.CopyTo(ms);
        return ms.ToArray();
    }
}
Nathan Phillips
fonte
6
Qual a diferença entre a sua resposta e a de Jon? Também devo fazer isso input.Position = 0 para que o CopyTo funcione.
Jeff
1
@nathan, readig um arquivo do web client (tamanho do arquivo = 1mb) - o iis terá que carregar o 1mb inteiro na sua memória, certo?
Royi Namir
5
@ Jeff, minha resposta só funcionará no .Net 4 ou acima, o Jons funcionará em versões inferiores, reescrevendo a funcionalidade fornecida a nós na versão posterior. Você está certo de que CopyTo copiará apenas da posição atual, se você tiver um fluxo Seekable e desejar copiar desde o início, poderá passar para o início usando seu código ou entrada.Seek (0, SeekOrigin.Begin), embora, em muitos casos, seu fluxo possa não ser pesquisável.
Nathan Phillips
5
pode valer a pena verificar se inputjá é um MemorySteamcurto-circuito. Eu sei que seria estúpido o chamador passar um MemoryStreammas ...
Jodrell 27/03
3
@ Jodrell, exatamente. Se você está copiando milhões de pequenos riachos na memória e um deles é um MemoryStream, em seguida, se a otimização faz sentido em seu contexto é a comparação do tempo necessário para fazer milhões de conversões de tipo contra o tempo necessário para copiar o que é um MemoryStreamem outro MemoryStream.
22615 Nathan Phillips
114

Só quero salientar que, caso você tenha um MemoryStream, já o possui memorystream.ToArray().

Além disso, se você estiver lidando com fluxos de subtipos desconhecidos ou diferentes e puder receber um MemoryStream, poderá retransmitir o referido método para esses casos e ainda usar a resposta aceita para os outros, assim:

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
fonte
1
Huh, para que servem todos os votos positivos? Mesmo com as suposições mais generosas, isso funciona apenas para fluxos que já são MemoryStreams. É claro que o exemplo também está obviamente incompleto, na forma como está usando uma variável não inicializada.
Roman Starkov
3
É isso mesmo, obrigado por apontar isso. O ponto ainda significa MemoryStream, então eu o corrigi para refletir isso.
Fernando Neira
Apenas mencione que, para o MemoryStream, outra possibilidade é MemoryStream.GetBuffer (), embora haja algumas dicas envolvidas. Veja stackoverflow.com/questions/1646193/… e krishnabhargav.blogspot.dk/2009/06/…
RenniePet
4
Isso realmente introduz um bug no código do Skeet; Se você ligar stream.Seek(1L, SeekOrigin.Begin), antes de chamar prontamente, se o fluxo for um fluxo de memória, você receberá 1 byte a mais do que se fosse qualquer outro fluxo. Se o chamador espera ler de onde a posição atual está no final do fluxo, você não deve usar CopyToou ToArray(); Na maioria dos casos, isso não será um problema, mas se o chamador não souber sobre esse comportamento peculiar, ele ficará confuso.
leat
67
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();
Sandip Patel
fonte
9
MemoryStream deve ser criado com "novo MemoryStream (file.PostedFile.ContentLength)" para evitar a fragmentação da memória.
Dan Randolph
52

apenas alguns centavos ... a prática que costumo usar é organizar os métodos como este como um auxiliar personalizado

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

adicione espaço para nome ao arquivo de configuração e use-o em qualquer lugar que desejar

Mr. Pumpkin
fonte
5
Observe que isso não funcionará no .NET 3.5 e abaixo, pois CopyTonão estava disponível Streamaté 4.0.
Tim
16

Você pode simplesmente usar o método ToArray () da classe MemoryStream, por exemplo,

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

Você pode até torná-lo mais sofisticado com extensões:

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, em seguida, chame-o como um método regular:

byte[] arr = someStream.ToByteArray()
Michal T
fonte
67
Eu acho que é uma má idéia colocar o fluxo de entrada em um bloco usando. Essa responsabilidade deve recair sobre o procedimento de chamada.
22413 Jeff Jeff
7

Eu recebo um erro de tempo de compilação com o código de Bob (ou seja, o questionador). Stream.Length é longo, enquanto BinaryReader.ReadBytes usa um parâmetro inteiro. No meu caso, não espero lidar com Streams grandes o suficiente para exigir precisão longa, portanto, uso o seguinte:

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

Caso alguém goste, aqui está uma solução somente .NET 4+ formada como um método de extensão sem a chamada desnecessária Dispose no MemoryStream. Essa é uma otimização irremediavelmente trivial, mas vale a pena notar que falhar em Dispose a MemoryStream não é uma falha real.

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

O que está acima está ok ... mas você encontrará corrupção de dados quando enviar coisas pelo SMTP (se necessário). Alterei para outra coisa que ajudará a enviar corretamente byte por 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;
        }'
NothinRandom
fonte
Não vejo onde esse código evita a corrupção de dados. Você pode explicar isso?
Nippey
Digamos que você tenha uma foto e deseja enviá-la via SMTP. Você provavelmente usará a codificação base64. Por algum motivo, o arquivo é corrompido se você o dividir em bytes. No entanto, o uso de um leitor binário permitirá que o arquivo seja enviado com êxito.
NothinRandom
3
Um pouco antigo, mas eu acho que isso merece menção - a implementação @NothinRandom fornece obras com strings, não streams. Provavelmente seria mais simples usar o File.ReadAllBytes nesse caso.
XwipeoutX
1
Votos negativos por causa do estilo de código perigoso (sem descarte / uso automático).
Arn # 1/17
Infelizmente, apenas -1 permitiu, nada a ver com a pergunta, parâmetro de nome de arquivo chamado input, não descartando, sem buffer de leitura, sem modo de arquivo e leitor binário para ler byte a byte, por que?
Aridane Álamo
2

Crie uma classe auxiliar e faça referência a ela em qualquer lugar que você deseja usá-la.

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

No espaço de nomes RestSharp.Extensions, existe o método ReadAsBytes. Dentro desse método, é usado o MemoryStream e existe o mesmo código, como em alguns exemplos desta página, mas quando você está usando o RestSharp, é a maneira mais fácil.

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

Você pode usar este método de extensão.

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

Esta é a função que estou usando, testada e funcionou bem. lembre-se de que 'input' não deve ser nulo e 'input.position' deve ser redefinido para '0' antes da leitura, caso contrário, ele interromperá o loop de leitura e nada será lido para converter em 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.S
fonte
-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
fonte
Você acabou de copiar o código das respostas 1 e 3 sem adicionar nada valioso. Por favor, não faça isso. :)
CodeCaster 17/17/17
Ao adicionar um código, descreva também a solução proposta em breve.
yakobom
-5

Consegui fazê-lo funcionar em uma única linha:

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

conforme esclarecido por johnnyRose , o código acima funcionará apenas para o MemoryStream

Abba
fonte
2
E se localStreamnão for um MemoryStream? Este código falhará.
johnnyRose
localStream deve ser um objeto baseado em fluxo. mais sobre o objeto baseado em fluxo aqui stackoverflow.com/questions/8156896/…
Abba
1
O que eu estava tentando sugerir é que, se você tentar lançar localStreama um MemoryStream, mas localStreamé não um MemoryStream, ele irá falhar. Esse código será compilado corretamente, mas poderá falhar no tempo de execução, dependendo do tipo real de localStream. Você nem sempre pode converter arbitrariamente um tipo base para um tipo filho; leia mais aqui . Este é outro bom exemplo que explica por que você nem sempre pode fazer isso.
johnnyRose
Para elaborar meu comentário acima: todos os MemoryStreams são Streams, mas nem todos os Streams são MemoryStreams.
johnnyRose
todos os objetos baseados em fluxo têm o fluxo como tipo base. E o próprio fluxo sempre pode ser convertido em fluxo de memória. Não importa qual objeto baseado em fluxo você tente converter no Meomry Stream, ele sempre deve funcionar. Nosso objetivo aqui é converter o objeto de fluxo em matriz de bytes. Você pode me dar um caso de sinalização onde falhará?
Abba