Comment enregistrer un flux dans un fichier en C #?

713

J'ai un StreamReaderobjet que j'ai initialisé avec un flux, maintenant je veux enregistrer ce flux sur le disque (le flux peut être un .gifou .jpgou .pdf).

Code existant:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. J'ai besoin de l'enregistrer sur le disque (j'ai le nom du fichier).
  2. À l'avenir, je voudrai peut-être stocker cela sur SQL Server.

J'ai également le type d'encodage, dont j'aurai besoin si je le stocke sur SQL Server, correct?

Loadman
la source
1
Qu'est-ce que myOtherObject?
anhtv13
2
Toujours pas de réponse acceptée sur cette question?
Brett Rigby
@BrettRigby Il y a une réponse de Jon Skeet, elle est à peu près automatiquement acceptée: D
Ricardo Dias Morais

Réponses:

913

Comme l'a souligné Tilendor dans la réponse de Jon Skeet, les flux ont une CopyTométhode depuis .NET 4.

var fileStream = File.Create("C:\\Path\\To\\File");
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
fileStream.Close();

Ou avec la usingsyntaxe:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}
Antoine Leclair
la source
67
Notez que vous devez appeler myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)si vous n'êtes pas déjà au début ou si vous ne copiez pas le flux entier.
Steve Rukuts
3
Si ce flux d'entrée est obtenu à partir de la connexion http, va-t-il mettre en mémoire tampon et télécharger puis écrire tous les octets de la source ?????
dbw
2
J'ai créé une visionneuse PDF où j'utilise stream, une fois que je lie le flux et quand j'enregistre le fichier pdf en utilisant le même stream puis sans utiliser "Seek (0, SeekOrigin.Begin)", je ne pourrai pas enregistrer le document correct. donc +1 pour avoir mentionné ce "Seek (0, SeekOrigin.Begin)"
user2463514
myOtherObject.InputStream.CopyTo (fileStream); cette ligne donne une erreur: accès refusé.
sulhadin
2
myOtherObject ??
Harry
531

Vous ne devez pas utiliser StreamReaderpour les fichiers binaires (comme les gifs ou jpgs). StreamReaderest pour les données texte . Vous perdrez presque certainement des données si vous les utilisez pour des données binaires arbitraires. (Si vous utilisez Encoding.GetEncoding (28591), vous serez probablement d'accord, mais à quoi ça sert?)

Pourquoi avez-vous besoin d'utiliser un StreamReader? Pourquoi ne pas simplement conserver les données binaires comme binaires et les réécrire sur le disque (ou SQL) en tant que données binaires?

EDIT: Comme cela semble être quelque chose les gens veulent voir ... si vous ne voulez juste copier un flux à un autre (par exemple dans un fichier) utiliser quelque chose comme ceci:

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

Pour l'utiliser pour vider un flux dans un fichier, par exemple:

using (Stream file = File.Create(filename))
{
    CopyStream(input, file);
}

Notez que cela a Stream.CopyToété introduit dans .NET 4, servant essentiellement le même objectif.

Jon Skeet
la source
6
Cela semble être un cas si courant que je suis surpris que ce ne soit pas en .NET. Je vois des gens créer des tableaux d'octets de la taille de tout le fichier, ce qui peut causer des problèmes pour les gros fichiers.
Tilendor du
81
@Tilendor: Il est présent en tant que méthode d'extension dans .NET 4. (CopyTo)
Jon Skeet
33
Je ne pense pas que ce soit une méthode d'extension, mais c'est nouveau dans la classe Stream.
Kugel
9
@Kugel: Vous avez raison, désolé. Je l' avais comme méthode d'extension dans une bibliothèque d'utilitaires, mais maintenant qu'elle est dans Stream elle-même, ma méthode d'extension n'est plus appelée.
Jon Skeet,
4
@Florian: C'est raisonnablement arbitraire - une valeur suffisamment petite pour éviter de prendre trop de mémoire et suffisamment grande pour transférer un morceau raisonnable à la fois. Ce serait bien d'être 16K, 32K peut-être - je ferais juste attention à ne pas finir sur le tas d'objets volumineux.
Jon Skeet
77
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}
Darren Corbett
la source
28
Vous ne devriez probablement pas mettre l' streamobjet using(){}entre crochets. Votre méthode n'a pas créé le flux, elle ne doit donc pas en disposer.
LarsTech
2
Au lieu de cela, vous devez utiliser à la FileStreamplace, sinon il restera ouvert jusqu'à ce qu'il soit récupéré.
Pavel Chikulaev
J'ai trouvé que votre approche était beaucoup plus proche pour résoudre mon problème dans WinForms avec ma classe de passerelle de classe AWS S3! Merci!
Luiz Eduardo
2
Cela a bien fonctionné mais j'ai obtenu une sortie de 0 Ko. Au lieu de cela , je devais le faire pour la sortie correcte: File.WriteAllBytes(destinationFilePath, input.ToArray());. Dans mon cas, inputest un MemoryStreamvenant de l'intérieur d'un ZipArchive.
SNag
23
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}
jhonjairoroa87
la source
1
Cela a bien fonctionné mais j'ai obtenu une sortie de 0 Ko. Au lieu de cela , je devais le faire pour la sortie correcte: File.WriteAllBytes(destinationFilePath, input.ToArray());. Dans mon cas, inputest un MemoryStreamvenant de l'intérieur d'un ZipArchive.
SNag
2
Cela m'a aidé à comprendre ce que je faisais mal. Cependant, n'oubliez pas de passer au début du flux: stream.Seek(0, SeekOrigin.Begin);
Nathan Bills
9

Je n'obtiens pas toutes les réponses en utilisant CopyTo, où peut-être que les systèmes utilisant l'application n'ont peut-être pas été mis à niveau vers .NET 4.0+. Je sais que certains voudraient forcer les gens à mettre à niveau, mais la compatibilité est également très bien aussi.

Une autre chose, je n'utilise pas un flux pour copier à partir d'un autre flux en premier lieu. Pourquoi ne pas simplement faire:

byte[] bytes = myOtherObject.InputStream.ToArray();

Une fois que vous avez les octets, vous pouvez facilement les écrire dans un fichier:

public static void WriteFile(string fileName, byte[] bytes)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write))
    {
        fs.Write(bytes, 0, (int)bytes.Length);
        //fs.Close();
    }
}

Ce code fonctionne comme je l'ai testé avec un .jpgfichier, mais j'avoue ne l'avoir utilisé qu'avec de petits fichiers (moins de 1 Mo). Un flux, pas de copie entre les flux, aucun encodage nécessaire, il suffit d'écrire les octets! Pas besoin de trop compliquer les choses StreamReadersi vous avez déjà un flux avec lequel vous pouvez convertir bytesdirectement .ToArray()!

Seuls les inconvénients potentiels que je peux voir en procédant de cette façon sont s'il y a un gros fichier que vous possédez, l'avoir comme flux et utiliser .CopyTo()ou équivalent permet FileStreamde le diffuser au lieu d'utiliser un tableau d'octets et de lire les octets un par un. En conséquence, cela pourrait être plus lent à faire de cette façon. Mais il ne devrait pas s'étouffer puisque la .Write()méthode des FileStreampoignées écrivant les octets, et il ne le fait qu'un octet à la fois, donc il n'encombrera pas la mémoire, sauf que vous devrez avoir suffisamment de mémoire pour contenir le flux en tant que byte[]objet . Dans ma situation où j'utilisais cela, obtenir un OracleBlob, je devais aller à un byte[], c'était assez petit, et en plus, il n'y avait pas de streaming disponible pour moi, de toute façon, alors j'ai juste envoyé mes octets à ma fonction, ci-dessus.

Une autre option, en utilisant un flux, serait de l'utiliser avec la CopyStreamfonction de Jon Skeet qui était dans un autre post - cela permet simplement FileStreamde prendre le flux d'entrée et de créer le fichier directement à partir de celui-ci. Il n'utilise pas File.Create, comme il l'a fait (ce qui au départ semblait être problématique pour moi, mais a trouvé plus tard que c'était probablement juste un bug VS ...).

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

public static void WriteFile(string fileName, Stream inputStream)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write)
    {
        CopyStream(inputStream, fs);
    }

    inputStream.Close();
    inputStream.Flush();
}
vapcguy
la source
1
Pas besoin d'appeler à Closecause deusing()
Alex78191
@ Alex78191 Si vous parlez inputStream.Close(), regardez à nouveau - inputStreamest envoyé en tant que variable. Le usingest sur le path+filenameflux de sortie. Si vous parliez fs.Close()au milieu de la using, désolé, vous aviez raison à ce sujet et je l'ai supprimé.
vapcguy
8
//If you don't have .Net 4.0  :)

public void SaveStreamToFile(Stream stream, string filename)
{  
   using(Stream destination = File.Create(filename))
      Write(stream, destination);
}

//Typically I implement this Write method as a Stream extension method. 
//The framework handles buffering.

public void Write(Stream from, Stream to)
{
   for(int a = from.ReadByte(); a != -1; a = from.ReadByte())
      to.WriteByte( (byte) a );
}

/*
Note, StreamReader is an IEnumerable<Char> while Stream is an IEnumbable<byte>.
The distinction is significant such as in multiple byte character encodings 
like Unicode used in .Net where Char is one or more bytes (byte[n]). Also, the
resulting translation from IEnumerable<byte> to IEnumerable<Char> can loose bytes
or insert them (for example, "\n" vs. "\r\n") depending on the StreamReader instance
CurrentEncoding.
*/
George
la source
16
La copie d'un flux octet par octet (en utilisant ReadByte / WriteByte) sera beaucoup plus lente que la copie tampon par tampon (en utilisant Read (byte [], int, int) / Write (byte [], int, int)).
Kevin
6

Pourquoi ne pas utiliser un objet FileStream?

public void SaveStreamToFile(string fileFullPath, Stream stream)
{
    if (stream.Length == 0) return;

    // Create a FileStream object to write a stream to a file
    using (FileStream fileStream = System.IO.File.Create(fileFullPath, (int)stream.Length))
    {
        // Fill the bytes[] array with the stream data
        byte[] bytesInStream = new byte[stream.Length];
        stream.Read(bytesInStream, 0, (int)bytesInStream.Length);

        // Use FileStream object to write to the specified file
        fileStream.Write(bytesInStream, 0, bytesInStream.Length);
     }
}
Adrian
la source
46
que se passe-t-il si le flux d'entrée fait 1 Go de long - ce code essaiera d'allouer 1 Go de tampon :)
Buthrakaur
1
Cela ne fonctionne pas avec ResponseStream, car il est de longueur inconnue.
Tomas Kubes
Bien qu'il soit vrai que vous devez avoir la mémoire disponible pour le byte[], je pense qu'il serait rare que vous diffusiez un blob de 1 Go + dans un fichier ... à moins que vous n'ayez un site qui conserve les torrents DVD ... Plus , la plupart des ordinateurs ont au moins 2 Go de RAM disponibles de nos jours, de toute façon .... La mise en garde est valide, mais je pense que c'est un cas où c'est probablement "assez bon" pour la plupart des travaux.
vapcguy
Les serveurs Web ne toléreront pas du tout un cas comme celui-ci, à moins que le site Web n'ait qu'un seul utilisateur actif à la fois.
NateTheGreatt
6

Une autre option consiste à obtenir le flux byte[]et à l'utiliser File.WriteAllBytes. Cela devrait faire:

using (var stream = new MemoryStream())
{
    input.CopyTo(stream);
    File.WriteAllBytes(file, stream.ToArray());
}

L'encapsuler dans une méthode d'extension lui donne un meilleur nom:

public void WriteTo(this Stream input, string file)
{
    //your fav write method:

    using (var stream = File.Create(file))
    {
        input.CopyTo(stream);
    }

    //or

    using (var stream = new MemoryStream())
    {
        input.CopyTo(stream);
        File.WriteAllBytes(file, stream.ToArray());
    }

    //whatever that fits.
}
nawfal
la source
3
Si l'entrée est trop grande, vous obtiendrez une exception de mémoire insuffisante. L'option de copier le contenu du flux d'entrée vers un flux de fichiers est bien meilleure
Ykok
4
public void testdownload(stream input)
{
    byte[] buffer = new byte[16345];
    using (FileStream fs = new FileStream(this.FullLocalFilePath,
                        FileMode.Create, FileAccess.Write, FileShare.None))
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
             fs.Write(buffer, 0, read);
        }
    }
}
Angelo
la source
Fournir un flux d'entrée mis en mémoire tampon directement au FileStream- sympa!
vapcguy
3

Voici un exemple qui utilise les utilisations appropriées et l'implémentation de idisposable:

static void WriteToFile(string sourceFile, string destinationfile, bool append = true, int bufferSize = 4096)
{
    using (var sourceFileStream = new FileStream(sourceFile, FileMode.OpenOrCreate))
    {
        using (var destinationFileStream = new FileStream(destinationfile, FileMode.OpenOrCreate))
        {
            while (sourceFileStream.Position < sourceFileStream.Length)
            {
                destinationFileStream.WriteByte((byte)sourceFileStream.ReadByte());
            }
        }
    }
}

... et il y a aussi ça

    public static void WriteToFile(FileStream stream, string destinationFile, int bufferSize = 4096, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.ReadWrite)
    {
        using (var destinationFileStream = new FileStream(destinationFile, mode, access, share))
        {
            while (stream.Position < stream.Length) 
            {
                destinationFileStream.WriteByte((byte)stream.ReadByte());
            }
        }
    }

La clé est de comprendre la bonne utilisation de l'utilisation (qui doit être implémentée lors de l'instanciation de l'objet qui implémente idisposable comme indiqué ci-dessus), et d'avoir une bonne idée de la façon dont les propriétés fonctionnent pour les flux. La position est littéralement l'index dans le flux (qui commence à 0) qui est suivi lorsque chaque octet est lu à l'aide de la méthode readbyte. Dans ce cas, je l'utilise essentiellement à la place d'une variable de boucle for et je la laisse simplement suivre jusqu'à la longueur qui est LITTÉRALEMENT la fin de l'ensemble du flux (en octets). Ignorez-le en octets car il est pratiquement le même et vous aurez quelque chose de simple et d'élégant comme celui-ci qui résout tout proprement.

Gardez également à l'esprit que la méthode ReadByte convertit simplement l'octet en entier dans le processus et peut simplement être reconvertie.

Je vais ajouter une autre implémentation que j'ai récemment écrite pour créer une sorte de tampon dynamique pour garantir l'écriture séquentielle des données afin d'éviter une surcharge massive

private void StreamBuffer(Stream stream, int buffer)
{
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        var memoryBuffer = memoryStream.GetBuffer();

        for (int i = 0; i < memoryBuffer.Length;)
        {
            var networkBuffer = new byte[buffer];
            for (int j = 0; j < networkBuffer.Length && i < memoryBuffer.Length; j++)
            {
                networkBuffer[j] = memoryBuffer[i];
                i++;
            }
            //Assuming destination file
            destinationFileStream.Write(networkBuffer, 0, networkBuffer.Length);
        }
    }
}

L'explication est assez simple: nous savons que nous devons garder à l'esprit l'ensemble des données que nous souhaitons écrire et aussi que nous ne voulons écrire que certains montants, nous voulons donc que la première boucle avec le dernier paramètre vide (identique à while ). Ensuite, nous initialisons un tampon de tableau d'octets qui est défini à la taille de ce qui est passé, et avec la deuxième boucle, nous comparons j à la taille du tampon et à la taille de l'original, et s'il est supérieur à la taille de l'original tableau d'octets, terminer l'analyse.

Andrew Ramshaw
la source