Création d'un tableau d'octets à partir d'un flux


913

Quelle est la méthode préférée pour créer un tableau d'octets à partir d'un flux d'entrée?

Voici ma solution actuelle avec .NET 3.5.

Stream s;
byte[] b;

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

Est-ce toujours une meilleure idée de lire et d'écrire des morceaux du flux?


60
Bien sûr, une autre question est de savoir si vous créez un octet [] à partir d'un flux ... pour les données volumineuses, il est préférable de traiter le flux comme, eh bien, un flux!
Marc Gravell

2
En effet, vous devriez probablement utiliser un flux au lieu d'un octet []. Mais il existe certaines API système qui ne prennent pas en charge les flux. Par exemple, vous ne pouvez pas créer un X509Certificate2 à partir d'un flux, vous devez lui donner un octet [] (ou une chaîne). Dans ce cas, c'est bien car un certificat x509 n'est probablement pas de grandes données .
0xced

Réponses:


1295

Cela dépend vraiment de votre confiance s.Length. Pour de nombreux flux, vous ne savez tout simplement pas combien de données il y aura. Dans de tels cas - et avant .NET 4 - j'utiliserais du code comme ceci:

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

Avec .NET 4 et supérieur, j'utiliserais Stream.CopyTo, ce qui est fondamentalement équivalent à la boucle dans mon code - créez le MemoryStream, appelez stream.CopyTo(ms)puis revenez ms.ToArray(). Travail accompli.

Je devrais peut-être expliquer pourquoi ma réponse est plus longue que les autres. Stream.Readne garantit pas qu'il lira tout ce qu'il a demandé. Si vous lisez un flux réseau, par exemple, il peut lire la valeur d'un paquet puis revenir, même s'il y aura bientôt plus de données. BinaryReader.Readcontinuera jusqu'à la fin du flux ou à votre taille spécifiée, mais vous devez toujours connaître la taille pour commencer.

La méthode ci-dessus continuera à lire (et à copier dans a MemoryStream) jusqu'à ce qu'il n'y ait plus de données. Il demande ensuite à MemoryStreamde renvoyer une copie des données dans un tableau. Si vous connaissez la taille pour commencer - ou pensez que vous connaissez la taille, sans être sûr - vous pouvez construire la MemoryStreamtaille pour commencer. De même, vous pouvez mettre un chèque à la fin, et si la longueur du flux est de la même taille que le tampon (renvoyé par MemoryStream.GetBuffer), vous pouvez simplement retourner le tampon. Le code ci-dessus n'est donc pas tout à fait optimisé, mais sera au moins correct. Il n'assume aucune responsabilité pour la fermeture du flux - l'appelant devrait le faire.

Consultez cet article pour plus d'informations (et une implémentation alternative).


9
@Jon, il vaut peut-être la peine de mentionner yoda.arachsys.com/csharp/readbinary.html
Sam Saffron

6
@Jeff: Nous n'avons pas vraiment le contexte ici, mais si vous avez écrit dans un flux, alors oui vous devez le "rembobiner" avant de le lire. Il n'y a qu'un "curseur" indiquant où vous vous trouvez dans le flux - pas un pour la lecture et un autre pour l'écriture.
Jon Skeet

5
@Jeff: C'est la responsabilité de l'appelant. Après tout, le flux peut ne pas être recherché (par exemple, un flux réseau) ou il peut tout simplement ne pas être nécessaire de le rembobiner.
Jon Skeet

18
Puis-je demander pourquoi 16*1024spécifiquement?
Anyname Donotcare

5
@just_name: Je ne sais pas si cela a une signification, mais (16 * 1024) se trouve être la moitié de Int16.MaxValue :)
caesay

735

Bien que la réponse de Jon soit correcte, il réécrit du code qui existe déjà dans CopyTo. Donc, pour .Net 4, utilisez la solution de Sandip, mais pour la version précédente de .Net, utilisez la réponse de Jon. Le code de Sandip serait amélioré en utilisant «using», car dans CopyTode nombreuses situations, les exceptions sont très probables et ne le laisseraient MemoryStreampas de côté.

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

6
Quelle différence cela fait-il entre votre réponse et celle de Jon? Je dois également faire cette entrée. Position = 0 pour que CopyTo fonctionne.
Jeff

1
@nathan, readig un fichier depuis le client web (filizesize = 1mb) - l'iis devra charger le 1mb entier dans sa mémoire, n'est-ce pas?
Royi Namir

5
@Jeff, ma réponse ne fonctionnera que sur .Net 4 ou supérieur, Jons fonctionnera sur les versions inférieures en réécrivant les fonctionnalités qui nous sont fournies dans la version ultérieure. Vous avez raison de dire que CopyTo ne copiera qu'à partir de la position actuelle, si vous avez un flux Seekable et que vous souhaitez copier depuis le début, vous pouvez passer au début en utilisant votre code ou votre entrée.Seek (0, SeekOrigin.Begin), mais dans de nombreux cas, votre flux peut ne pas être recherché.
Nathan Phillips

5
il peut être utile de vérifier s'il inputexiste déjà un MemorySteamcourt-circuit. Je sais que ce serait stupide de la part de l'appelant de passer un MemoryStreammais ...
Jodrell

3
@Jodrell, exactement. Si vous copiez des millions de petits flux dans la mémoire et que l'un d'entre eux est de MemoryStreamsavoir si l'optimisation est logique dans votre contexte, c'est la comparaison du temps nécessaire pour effectuer des millions de conversions de type par rapport au temps nécessaire pour copier celui qui est MemoryStreamdans un autre MemoryStream.
Nathan Phillips

114

Je veux juste souligner que si vous avez un MemoryStream, vous en avez déjà memorystream.ToArray().

De plus, si vous avez affaire à des flux de sous-types inconnus ou différents et que vous pouvez en recevoir un MemoryStream, vous pouvez relayer cette méthode pour ces cas et toujours utiliser la réponse acceptée pour les autres, comme ceci:

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

1
Huh, à quoi servent tous les votes positifs? Même avec les hypothèses les plus généreuses, cela ne fonctionne que pour les flux qui sont déjà MemoryStreams. Bien sûr, l'exemple est également manifestement incomplet, dans la façon dont il utilise une variable non initialisée.
Roman Starkov

3
C'est vrai, merci de l'avoir signalé. Le point représente toujours MemoryStream, donc je l'ai corrigé pour refléter cela.
Fernando Neira

Il suffit de mentionner que pour MemoryStream une autre possibilité est MemoryStream.GetBuffer (), bien qu'il y ait quelques pièges impliqués. Voir stackoverflow.com/questions/1646193/… et krishnabhargav.blogspot.dk/2009/06/…
RenniePet

4
Cela introduit en fait un bogue dans le code de Skeet; Si vous appelez stream.Seek(1L, SeekOrigin.Begin), avant d'appeler en lecture, si le flux est un flux de mémoire, vous obtiendrez 1 octet de plus que s'il s'agit d'un autre flux. Si l'appelant s'attend à lire depuis la position actuelle jusqu'à la fin du flux, vous ne devez pas utiliser CopyToou ToArray(); Dans la plupart des cas, ce ne sera pas un problème, mais si l'appelant ne connaît pas ce comportement excentrique, il sera confus.
LEAT

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

9
MemoryStream doit être créé avec "new MemoryStream (file.PostedFile.ContentLength)" pour éviter la fragmentation de la mémoire.
Dan Randolph

52

juste mes quelques cents ... la pratique que j'utilise souvent est d'organiser les méthodes comme celle-ci en tant qu'assistant personnalisé

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

ajouter un espace de noms au fichier de configuration et l'utiliser où vous le souhaitez


5
Notez que cela ne fonctionnera pas dans .NET 3.5 et inférieur car il CopyTon'était pas disponible Streamavant 4.0.
Tim

16

Vous pouvez simplement utiliser la méthode ToArray () de la classe MemoryStream, par exemple

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

10

Vous pouvez même le rendre plus sophistiqué avec des extensions:

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

Et puis appelez-le comme une méthode régulière:

byte[] arr = someStream.ToByteArray()

67
Je pense que c'est une mauvaise idée de mettre le flux d'entrée dans un bloc à l'aide. Cette responsabilité devrait incomber à la procédure d'appel.
Jeff

7

J'obtiens une erreur de compilation avec le code de Bob (c'est-à-dire le questionneur). Stream.Length est long tandis que BinaryReader.ReadBytes prend un paramètre entier. Dans mon cas, je ne m'attends pas à traiter des flux suffisamment grands pour nécessiter une longue précision, j'utilise donc les éléments suivants:

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

Si quelqu'un l'aime, voici une seule solution .NET 4+ formée comme une méthode d'extension sans l'appel inutile Dispose sur le MemoryStream. Il s'agit d'une optimisation désespérément triviale, mais il convient de noter que l'échec de l'élimination d'un MemoryStream n'est pas un véritable échec.

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

3

Celui ci-dessus est correct ... mais vous rencontrerez une corruption de données lorsque vous envoyez des trucs via SMTP (si vous en avez besoin). J'ai changé pour quelque chose d'autre qui aidera à envoyer correctement octet par octet: '

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

Je ne vois pas où ce code évite la corruption des données. Pouvez-vous l'expliquer?
Nippey

Disons que vous avez une photo et que vous souhaitez l'envoyer via SMTP. Vous utiliserez probablement l'encodage base64. Pour une raison quelconque, le fichier est corrompu si vous le décomposez en octets. Cependant, l'utilisation d'un lecteur binaire permettra d'envoyer le fichier avec succès.
NothinRandom

3
Un peu vieux, mais je pense que cela mérite d'être mentionné - l'implémentation @NothinRandom fournit des œuvres avec des chaînes, pas des flux. Il serait probablement plus simple d'utiliser simplement File.ReadAllBytes dans ce cas, cependant.
XwipeoutX

1
Downvote en raison du style de code dangereux (pas d'élimination / utilisation automatique).
arni

Malheureusement, seulement -1 autorisé, rien à voir avec la question, le paramètre de nom de fichier nommé input, ne dispose pas, pas de tampon de lecture, pas de mode de fichier et un lecteur binaire pour lire octet par octet pourquoi?
Aridane Álamo

2

Créez une classe d'assistance et référencez-la partout où vous souhaitez l'utiliser.

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

2

Dans l'espace de noms RestSharp.Extensions, il y a la méthode ReadAsBytes. À l'intérieur de cette méthode est utilisé MemoryStream et il y a le même code que dans certains exemples sur cette page, mais lorsque vous utilisez RestSharp, c'est la manière la plus simple.

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

1

Vous pouvez utiliser cette méthode d'extension.

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

C'est la fonction que j'utilise, testée et qui a bien fonctionné. veuillez garder à l'esprit que 'input' ne doit pas être nul et 'input.position' doit être réinitialisé à '0' avant de lire sinon cela rompra la boucle de lecture et rien ne lira pour être converti en tableau.

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

Vous venez de copier le code des réponses # 1 et # 3 sans rien ajouter de précieux. S'il vous plaît ne faites pas ça. :)
CodeCaster

Lorsque vous ajoutez un code, décrivez également brièvement votre solution proposée.
yakobom

-5

j'ai pu le faire fonctionner sur une seule ligne:

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

comme clarifié par johnnyRose , le code ci-dessus ne fonctionnera que pour MemoryStream


2
Et si ce localStreamn'est pas un MemoryStream? Ce code échouera.
johnnyRose

localStream doit être un objet basé sur un flux. plus d'informations sur les objets basés sur les flux ici stackoverflow.com/questions/8156896/…
Abba

1
Ce que je voulais suggérer est, si vous essayez de casting localStreamà un MemoryStream, mais localStreamest pas un MemoryStream, il va échouer. Ce code se compilera correctement, mais il pourrait échouer lors de l'exécution, selon le type réel de localStream. Vous ne pouvez pas toujours convertir arbitrairement un type de base en type enfant; en savoir plus ici . Ceci est un autre bon exemple qui explique pourquoi vous ne pouvez pas toujours faire cela.
johnnyRose

Pour développer mon commentaire ci-dessus: tous les flux de mémoire sont des flux, mais tous les flux ne sont pas des flux de mémoire.
johnnyRose

tous les objets basés sur Stream ont Stream comme type de base. Et Stream lui-même peut toujours être converti en flux mémoire. Quel que soit l'objet basé sur le flux que vous essayez de caster sur Meomry Stream, cela devrait toujours fonctionner. Notre objectif ici est de convertir un objet de flux en tableau d'octets. Pouvez-vous me donner un étui où cela échouera?
Abba
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.