Wie speichere ich einen Stream in einer Datei in C #?

713

Ich habe ein StreamReaderObjekt, das ich mit einem Stream initialisiert habe. Jetzt möchte ich diesen Stream auf der Festplatte speichern (der Stream kann ein .gifoder .jpgoder sein .pdf).

Bestehender Code:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. Ich muss dies auf der Festplatte speichern (ich habe den Dateinamen).
  2. In Zukunft möchte ich dies möglicherweise in SQL Server speichern.

Ich habe auch den Codierungstyp, den ich benötige, wenn ich ihn auf SQL Server speichere, richtig?

Loadman
quelle
1
Was ist myOtherObject?
anhtv13
2
Noch keine akzeptierte Antwort auf diese Frage?
Brett Rigby
@BrettRigby Es gibt eine Antwort von Jon Skeet, die ziemlich automatisch akzeptiert wird: D
Ricardo Dias Morais

Antworten:

912

Wie Tilendor in Jon Skeets Antwort hervorgehoben hat, haben Streams CopyToseit .NET 4 eine Methode.

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

Oder mit der usingSyntax:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}
Antoine Leclair
quelle
67
Beachten Sie, dass Sie anrufen müssen, myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)wenn Sie noch nicht am Anfang stehen oder nicht den gesamten Stream kopieren möchten.
Steve Rukuts
3
Wenn dieser Eingabestream von einer http-Verbindung stammt, wird er gepuffert und heruntergeladen und dann alle Bytes von der Quelle geschrieben ?????
dbw
2
Ich habe einen PDF-Viewer erstellt, in dem ich Stream verwende. Sobald ich den Stream binde und wenn ich die PDF-Datei mit demselben Stream speichere, ohne "Seek (0, SeekOrigin.Begin)" zu verwenden, kann ich kein korrektes Dokument speichern. also +1 für die Erwähnung dieses "Seek (0, SeekOrigin.Begin)"
user2463514
myOtherObject.InputStream.CopyTo (fileStream); Diese Zeile gibt einen Fehler aus: Zugriff verweigert.
Sulhadin
2
myOtherObject ??
Harry
531

Sie müssen nicht verwenden StreamReaderfür binäre Dateien (wie GIF oder JPG - Dateien). StreamReaderist für Textdaten . Sie werden mit ziemlicher Sicherheit Daten verlieren, wenn Sie sie für beliebige Binärdaten verwenden. (Wenn Sie Encoding.GetEncoding (28591) verwenden, sind Sie wahrscheinlich in Ordnung, aber worum geht es?)

Warum brauchen Sie StreamReaderüberhaupt eine? Warum nicht einfach die Binärdaten als Binärdaten behalten und als Binärdaten auf die Festplatte (oder SQL) zurückschreiben?

EDIT: Da dies scheint etwas die Leute sehen wollen sein ... , wenn Sie es nur einen Stream in einen anderen kopieren möchten (zB in einer Datei) Verwendung etwas wie folgt aus :

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

So verwenden Sie es zum Speichern eines Streams in eine Datei, zum Beispiel:

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

Beachten Sie, dass dies Stream.CopyToin .NET 4 eingeführt wurde und im Wesentlichen demselben Zweck dient.

Jon Skeet
quelle
6
Dies scheint ein so häufiger Fall zu sein. Ich bin überrascht, dass es nicht in .NET ist. Ich sehe Leute, die Byte-Arrays in der Größe der gesamten Datei erstellen, was bei großen Dateien zu Problemen führen kann.
Tilendor
81
@ Tilendor: Es ist als Erweiterungsmethode in .NET 4 vorhanden. (CopyTo)
Jon Skeet
33
Ich denke nicht, dass es eine Erweiterungsmethode ist, aber es ist neu in der Stream-Klasse.
Kugel
9
@ Kugel: Du hast recht, sorry. Ich hatte es als Erweiterungsmethode in einer Dienstprogrammbibliothek, aber jetzt, da es in Stream selbst ist, wird meine Erweiterungsmethode nicht aufgerufen.
Jon Skeet
4
@Florian: Es ist einigermaßen willkürlich - ein Wert, der klein genug ist, um nicht zu viel Speicherplatz zu beanspruchen, und groß genug, um jeweils einen vernünftigen Teil zu übertragen. Es wäre in Ordnung, 16K, vielleicht 32K zu sein - ich würde nur darauf achten, nicht auf dem großen Objekthaufen zu landen.
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
quelle
28
Sie sollten das streamObjekt wahrscheinlich nicht in die using(){}Klammer setzen. Ihre Methode hat den Stream nicht erstellt, daher sollte er nicht entsorgt werden.
LarsTech
2
Stattdessen müssen Sie FileStreamstattdessen verwenden, sonst bleibt es offen, bis der Müll gesammelt wird.
Pavel Chikulaev
Ich habe festgestellt, dass Ihr Ansatz viel näher war, um mein Problem in WinForms mit meiner Gateway-Klasse der AWS S3-Klasse zu lösen! Vielen Dank!
Luiz Eduardo
2
Dies lief gut, aber ich habe eine 0 KB Ausgabe. Stattdessen musste ich dies für die korrekte Ausgabe tun : File.WriteAllBytes(destinationFilePath, input.ToArray());. In meinem Fall inputkommt ein MemoryStreamaus einem 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
quelle
1
Dies lief gut, aber ich habe eine 0 KB Ausgabe. Stattdessen musste ich dies für die korrekte Ausgabe tun : File.WriteAllBytes(destinationFilePath, input.ToArray());. In meinem Fall inputkommt ein MemoryStreamaus einem ZipArchive.
SNag
2
Dies half mir herauszufinden, was ich falsch machte. Vergessen Sie jedoch nicht, zum Anfang des Streams zu wechseln: stream.Seek(0, SeekOrigin.Begin);
Nathan Bills
9

Ich erhalte nicht alle Antworten mit CopyTo, wobei die Systeme, die die App verwenden, möglicherweise nicht auf .NET 4.0+ aktualisiert wurden. Ich weiß, einige möchten die Leute zum Upgrade zwingen, aber die Kompatibilität ist auch gut.

Eine andere Sache ist, dass ich überhaupt keinen Stream verwende, um von einem anderen Stream zu kopieren. Warum nicht einfach machen:

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

Sobald Sie die Bytes haben, können Sie sie einfach in eine Datei schreiben:

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

Dieser Code funktioniert so, wie ich ihn mit einer .jpgDatei getestet habe , obwohl ich zugeben muss, dass ich ihn nur mit kleinen Dateien (weniger als 1 MB) verwendet habe. Ein Stream, kein Kopieren zwischen Streams, keine Codierung erforderlich, schreiben Sie einfach die Bytes! Sie müssen die Dinge nicht zu kompliziert machen, StreamReaderwenn Sie bereits einen Stream haben, in den Sie bytesdirekt konvertieren können .ToArray()!

Die einzigen möglichen Nachteile, die ich auf diese Weise erkennen kann, sind, wenn Sie eine große Datei haben. Wenn Sie sie als Stream haben und sie verwenden .CopyTo()oder gleichwertig verwenden, können Sie FileStreamsie streamen, anstatt ein Byte-Array zu verwenden und die Bytes einzeln zu lesen. Infolgedessen könnte es auf diese Weise langsamer sein. Es sollte jedoch nicht ersticken, da die .Write()Methode der FileStreamHandles die Bytes schreibt, und es wird jeweils nur ein Byte ausgeführt, sodass der Speicher nicht verstopft wird, außer dass Sie über genügend Speicher verfügen müssen, um den Stream als zu speichern byte[]Objekt . In meiner Situation, in der ich dies verwendet habe, OracleBlobmusste ich zu einem gehen byte[], es war klein genug, und außerdem stand mir sowieso kein Streaming zur Verfügung, also habe ich meine Bytes einfach an meine Funktion oben gesendet.

Eine andere Möglichkeit, einen Stream zu verwenden, besteht darin, ihn mit Jon Skeets CopyStreamFunktion zu verwenden, die sich in einem anderen Beitrag befand. Hiermit wird lediglich FileStreamder Eingabestream verwendet und die Datei direkt daraus erstellt. Es wird nicht verwendet File.Create, wie er es tat (was anfangs für mich problematisch schien, aber später feststellte, dass es wahrscheinlich nur ein VS-Fehler war ...).

/// <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
quelle
1
Keine Notwendigkeit anzurufen Closewegenusing()
Alex78191
@ Alex78191 Wenn Sie sprechen inputStream.Close(), schauen Sie noch einmal - inputStreamwird als Variable gesendet. Das usingist im path+filenameAusgabestream. Wenn Sie fs.Close()mitten im gesprochen haben using, tut mir leid, Sie hatten Recht damit, und ich habe das entfernt.
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
quelle
16
Das Kopieren eines Streams Byte für Byte (mit ReadByte / WriteByte) ist viel langsamer als das Kopieren von Puffer für Puffer (mit Read (Byte [], int, int) / Write (Byte [], int, int)).
Kevin
6

Warum nicht ein FileStream-Objekt verwenden?

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
quelle
46
Was ist, wenn der Eingabestream 1 GB lang ist - dieser Code würde versuchen, 1 GB Puffer zuzuweisen :)
Buthrakaur
1
Dies funktioniert nicht mit ResponseStream, da es eine unbekannte Länge hat.
Tomas Kubes
Es stimmt zwar, dass Sie den Speicher für das zur Verfügung haben müssen byte[], aber ich denke, es ist selten, dass Sie einen 1 GB + Blob in eine Datei streamen ... es sei denn, Sie haben eine Site, auf der DVD-Torrents gespeichert sind ... Plus Die meisten Computer verfügen heutzutage sowieso über mindestens 2 GB RAM. Vorbehalt ist gültig, aber ich denke, dies ist ein Fall, in dem es wahrscheinlich "gut genug" für die meisten Jobs ist.
Vapcguy
Webserver tolerieren einen solchen Fall überhaupt nicht sehr gut, es sei denn, auf der Website ist nur ein einziger Benutzer gleichzeitig aktiv.
NateTheGreatt
6

Eine andere Möglichkeit ist, den Stream zu einem zu bringen byte[]und zu verwenden File.WriteAllBytes. Dies sollte tun:

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

Wenn Sie es in eine Erweiterungsmethode einschließen, erhalten Sie eine bessere Benennung:

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
quelle
3
Wenn die Eingabe zu groß ist, wird eine Ausnahme wegen Speichermangel angezeigt. Die Option, Inhalte aus dem Eingabestream in einen Dateistream zu kopieren, ist viel besser
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
quelle
Bereitstellung eines gepufferten Eingabestreams direkt an die FileStream- nice!
Vapcguy
3

Hier ist ein Beispiel, das die ordnungsgemäße Verwendung und Implementierung von idisposable verwendet:

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

... und da ist auch das

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

Der Schlüssel besteht darin, die ordnungsgemäße Verwendung von using zu verstehen (die bei der Instanziierung des Objekts implementiert werden sollte, das wie oben gezeigt idisposable implementiert), und eine gute Vorstellung davon zu haben, wie die Eigenschaften für Streams funktionieren. Die Position ist buchstäblich der Index innerhalb des Streams (der bei 0 beginnt), dem gefolgt wird, wenn jedes Byte mit der Readbyte-Methode gelesen wird. In diesem Fall verwende ich es im Wesentlichen anstelle einer for-Schleifenvariablen und lasse es einfach bis zu der Länge durchlaufen, die buchstäblich das Ende des gesamten Streams ist (in Bytes). Ignorieren Sie in Bytes, da es praktisch dasselbe ist und Sie etwas Einfaches und Elegantes wie dieses haben, das alles sauber auflöst.

Beachten Sie auch, dass die ReadByte-Methode das Byte dabei einfach in ein int umwandelt und einfach zurückkonvertiert werden kann.

Ich werde eine weitere Implementierung hinzufügen, die ich kürzlich geschrieben habe, um eine Art dynamischen Puffer zu erstellen, um sequentielle Datenschreibvorgänge sicherzustellen und eine massive Überlastung zu verhindern

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

Die Erklärung ist ziemlich einfach: Wir wissen, dass wir den gesamten Datensatz berücksichtigen müssen, den wir schreiben möchten, und dass wir nur bestimmte Mengen schreiben möchten. Daher möchten wir, dass die erste Schleife mit dem letzten Parameter leer ist (wie während) ). Als nächstes initialisieren wir einen Byte-Array-Puffer, der auf die Größe des übergebenen Objekts eingestellt ist, und vergleichen mit der zweiten Schleife j mit der Größe des Puffers und der Größe des Originals, und wenn er größer als die Größe des Originals ist Byte-Array, beenden Sie den Lauf.

Andrew Ramshaw
quelle