Crear una matriz de bytes a partir de una secuencia

913

¿Cuál es el método preferido para crear una matriz de bytes a partir de una secuencia de entrada?

Aquí está mi solución actual con .NET 3.5.

Stream s;
byte[] b;

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

¿Sigue siendo una mejor idea leer y escribir fragmentos de la secuencia?

Beto
fuente
6060
Por supuesto, otra pregunta es si debe crear un byte [] a partir de una secuencia ... para datos grandes, es preferible tratar la secuencia como, bueno, ¡una secuencia!
Marc Gravell
2
De hecho, probablemente debería usar una secuencia en lugar de un byte []. Pero hay algunas API del sistema que no admiten transmisiones. Por ejemplo, no puede crear un X509Certificate2 a partir de una secuencia, debe darle un byte [] (o una cadena). En este caso, está bien, ya que un certificado x509 probablemente no son datos grandes .
0xced

Respuestas:

1295

Realmente depende de si puedes confiar o no s.Length. Para muchas transmisiones, simplemente no sabes cuántos datos habrá. En tales casos, y antes de .NET 4, usaría un 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();
    }
}

Con .NET 4 y superior, usaría Stream.CopyTo, que es básicamente equivalente al bucle en mi código: crear MemoryStream, llamar stream.CopyTo(ms)y luego regresar ms.ToArray(). Trabajo hecho.

Quizás debería explicar por qué mi respuesta es más larga que las otras. Stream.Readno garantiza que leerá todo lo que se le solicite. Si está leyendo desde una transmisión de red, por ejemplo, puede leer el valor de un paquete y luego regresar, incluso si habrá más datos pronto. BinaryReader.Readcontinuará hasta el final de la transmisión o el tamaño especificado, pero aún debe saber el tamaño para comenzar.

El método anterior seguirá leyendo (y copiando en a MemoryStream) hasta que se quede sin datos. Luego le pide MemoryStreamque devuelva una copia de los datos en una matriz. Si conoce el tamaño para comenzar, o cree que conoce el tamaño, sin estar seguro, puede construir el MemoryStreamtamaño para comenzar. Del mismo modo, puede marcar al final, y si la longitud de la secuencia es del mismo tamaño que el búfer (devuelto por MemoryStream.GetBuffer), puede devolver el búfer. Entonces, el código anterior no está optimizado, pero al menos será correcto. No asume ninguna responsabilidad por cerrar la transmisión; la persona que llama debe hacerlo.

Consulte este artículo para obtener más información (y una implementación alternativa).

Jon Skeet
fuente
99
@ Jon, puede valer la pena mencionar yoda.arachsys.com/csharp/readbinary.html
Sam Saffron
66
@Jeff: Realmente no tenemos el contexto aquí, pero si has estado escribiendo en una secuencia, entonces sí, debes "rebobinarlo" antes de leerlo. Solo hay un "cursor" que dice dónde se encuentra dentro de la secuencia, no uno para leer y uno separado para escribir.
Jon Skeet
55
@Jeff: es responsabilidad de la persona que llama. Después de todo, el flujo puede no ser buscable (por ejemplo, un flujo de red) o simplemente no hay necesidad de rebobinarlo.
Jon Skeet
18
¿Puedo preguntar por qué 16*1024específicamente?
Anyname Donotcare el
55
@just_name: No sé si esto tiene algún significado, pero (16 * 1024) es la mitad de Int16.MaxValue :)
caesay
735

Si bien la respuesta de Jon es correcta, está reescribiendo el código que ya existe CopyTo. Entonces, para .Net 4 use la solución de Sandip, pero para la versión anterior de .Net use la respuesta de Jon. El código de Sandip se mejoraría mediante el uso de "usar", ya que las excepciones CopyToson, en muchas situaciones, bastante probables y dejarían lo MemoryStreamno dispuesto.

public static byte[] ReadFully(Stream input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        input.CopyTo(ms);
        return ms.ToArray();
    }
}
Nathan Phillips
fuente
66
¿Qué diferencia hay entre tu respuesta y la de Jon? También debo hacer esta entrada. Posición = 0 para que CopyTo funcione.
Jeff
1
@nathan, lea un archivo desde el cliente web (filizesize = 1mb): el iis tendrá que cargar todo el 1mb en su memoria, ¿verdad?
Royi Namir
55
@Jeff, mi respuesta solo funcionará en .Net 4 o superior, Jons trabajará en versiones inferiores reescribiendo la funcionalidad que se nos proporcionó en la versión posterior. Tiene razón en que CopyTo solo copiará desde la posición actual, si tiene una secuencia Seekable y desea copiar desde el principio, entonces puede moverse al principio usando su código o entrada.Seek (0, SeekOrigin.Begin), aunque en muchos casos su transmisión puede no ser Seekable.
Nathan Phillips
55
Puede valer la pena comprobar si inputya hay un MemorySteamcortocircuito. Sé que sería estúpido por parte de la persona que llama pasar un MemoryStreampero ...
Jodrell
3
@Jodrell, exactamente así. Si está copiando millones de pequeñas transmisiones en la memoria y una de ellas es una MemoryStreampregunta, entonces si la optimización tiene sentido en su contexto es la comparación del tiempo necesario para realizar millones de conversiones de tipos con el tiempo necesario para copiar la que está MemoryStreamen otra MemoryStream.
Nathan Phillips
114

Solo quiero señalar que en caso de que tenga un MemoryStream que ya tiene memorystream.ToArray()para eso.

Además, si se trata de secuencias de subtipos desconocidos o diferentes y puede recibir un MemoryStream, puede transmitir dicho método para esos casos y aún usar la respuesta aceptada para los demás, como esta:

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
fuente
1
¿Para qué son todos los votos a favor? Incluso con los supuestos más generosos, esto solo funciona para transmisiones que ya son MemoryStreams. Por supuesto, el ejemplo también es obviamente incompleto, en cuanto a cómo está utilizando una variable no inicializada.
Roman Starkov
3
Así es, gracias por señalar eso. Sin embargo, el punto sigue representando MemoryStream, así que lo arreglé para reflejar eso.
Fernando Neira
Solo mencione que para MemoryStream otra posibilidad es MemoryStream.GetBuffer (), aunque hay algunas trampas involucradas. Ver stackoverflow.com/questions/1646193/… y krishnabhargav.blogspot.dk/2009/06/…
RenniePet
44
Esto realmente introduce un error en el código de Skeet; Si llama stream.Seek(1L, SeekOrigin.Begin), antes de invocar con facilidad, si la secuencia es una secuencia de memoria, obtendrá 1 byte más que si se trata de cualquier otra secuencia. Si la persona que llama espera leer desde donde está la posición actual hasta el final de la transmisión, entonces no debe usar CopyToo ToArray(); En la mayoría de los casos, esto no será un problema, pero si la persona que llama no sabe acerca de este comportamiento peculiar, se confundirá.
leat
67
MemoryStream ms = new MemoryStream();
file.PostedFile.InputStream.CopyTo(ms);
var byts = ms.ToArray();
ms.Dispose();
Sandip Patel
fuente
99
MemoryStream debe crearse con "new MemoryStream (file.PostedFile.ContentLength)" para evitar la fragmentación de la memoria.
Dan Randolph
52

solo mi par de centavos ... la práctica que uso a menudo es organizar métodos como este como ayuda personalizada

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

agregue espacio de nombres al archivo de configuración y úselo donde desee

Señor calabaza
fuente
55
Tenga en cuenta que esto no funcionará en .NET 3.5 y CopyToversiones posteriores, ya que no estaba disponible Streamhasta 4.0.
Tim
16

Simplemente puede usar el método ToArray () de la clase MemoryStream, por ejemplo

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

Incluso puedes hacerlo más elegante con extensiones:

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

Y luego llámalo como un método regular:

byte[] arr = someStream.ToByteArray()
Michal T
fuente
67
Creo que es una mala idea poner la secuencia de entrada en un bloque de uso. Esa responsabilidad debe descansar en el procedimiento de llamada.
Jeff
7

Recibo un error de tiempo de compilación con el código de Bob (es decir, el interrogador). Stream.Length es largo mientras que BinaryReader.ReadBytes toma un parámetro entero. En mi caso, no espero tratar con Streams lo suficientemente grandes como para requerir una precisión larga, por lo que utilizo lo siguiente:

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

En caso de que a alguien le guste, aquí hay una solución única .NET 4+ formada como un método de extensión sin la innecesaria llamada Dispose en MemoryStream. Esta es una optimización irremediablemente trivial, pero vale la pena señalar que no eliminar un MemoryStream no es una falla real.

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

El anterior está bien ... pero encontrará daños en los datos cuando envíe cosas a través de SMTP (si es necesario). He cambiado a otra cosa que ayudará a enviar correctamente 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;
        }'
Nada al azar
fuente
No veo dónde este código evita la corrupción de datos. ¿Puedes explicarlo?
Nippey
Supongamos que tiene una foto y desea enviarla a través de SMTP. Probablemente usará la codificación base64. Por alguna razón, el archivo se corrompe si lo divide en bytes. Sin embargo, el uso de un lector binario permitirá que el archivo se envíe con éxito.
NothinRandom
3
Algo viejo, pero sentí que esto vale la pena mencionar: la implementación @NothinRandom proporciona trabajos con cadenas, no transmisiones. Sin embargo, probablemente sería más simple usar File.ReadAllBytes en este caso.
XwipeoutX
1
Voto negativo debido a un estilo de código peligroso (sin disposición / uso automático).
arni
Lamentablemente, solo se permite -1, nada que ver con la pregunta, el parámetro del nombre del archivo llamado input, sin disposición, sin búfer de lectura, sin modo de archivo y lector binario para leer byte a byte ¿por qué?
Aridane Álamo
2

Cree una clase auxiliar y haga referencia a ella en cualquier lugar donde desee usarla.

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

En el espacio de nombres RestSharp.Extensions hay un método ReadAsBytes. Dentro de este método se usa MemoryStream y hay el mismo código que en algunos ejemplos de esta página, pero cuando usa RestSharp, esta es la forma más fácil.

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

Puedes usar este método de extensión.

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

Esta es la función que estoy usando, probé y funcionó bien. tenga en cuenta que 'input' no debe ser nulo y 'input.position' debe restablecerse a '0' antes de leer, de lo contrario se romperá el ciclo de lectura y nada se leerá para convertirlo en matriz.

    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
fuente
-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
fuente
Acaba de copiar el código de la respuesta n. ° 1 y n. ° 3 sin agregar nada valioso. Por favor no hagas eso. :)
CodeCaster
Cuando agregue un código, también describa la solución propuesta en breve.
yakobom
-5

pude hacerlo funcionar en una sola línea:

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

según lo aclarado por johnnyRose , el código anterior solo funcionará para MemoryStream

Abba
fuente
2
¿Qué pasa si localStreamno es un MemoryStream? Este código fallará.
johnnyRose
localStream tiene que ser un objeto basado en flujo. Más información sobre el objeto basado en flujo aquí stackoverflow.com/questions/8156896/…
Abba
1
Lo que estaba tratando de sugerir es que, si intenta convertir localStreama una MemoryStream, pero localStreames no una MemoryStream, que va a fallar. Este código se compilará bien, pero podría fallar en tiempo de ejecución, dependiendo del tipo real de localStream. No siempre puede emitir arbitrariamente un tipo base a un tipo secundario; Lea más aquí . Este es otro buen ejemplo que explica por qué no siempre puedes hacer esto.
johnnyRose
Para ampliar mi comentario anterior: todos los MemoryStreams son Streams, pero no todos los Streams son MemoryStreams.
johnnyRose
Todos los objetos basados ​​en Stream tienen Stream como tipo base. Y Stream en sí mismo siempre puede ser convertible en flujo de memoria. No importa qué objeto basado en transmisión intente transmitir a Meomry Stream, siempre debería funcionar. Nuestro objetivo aquí es convertir el objeto de flujo en una matriz de bytes. ¿Me puede dar un caso único donde fallará?
Abba