Wie kopiere ich den Inhalt eines Streams in einen anderen?

521

Was ist der beste Weg, um den Inhalt eines Streams in einen anderen zu kopieren? Gibt es dafür eine Standarddienstprogrammmethode?

Anton
quelle
Vielleicht noch wichtiger an dieser Stelle, wie kopiert man den Inhalt "streambar", was bedeutet, dass er nur den Quell-Stream kopiert, wenn etwas den Ziel-Stream verbraucht ...?
Drzaus

Antworten:

694

Ab .NET 4.5 gibt es die Stream.CopyToAsyncMethode

input.CopyToAsync(output);

Dies gibt ein zurück Task, das fortgesetzt werden kann, wenn es abgeschlossen ist, wie folgt :

await input.CopyToAsync(output)

// Code from here on will be run in a continuation.

Beachten Sie, CopyToAsyncdass der folgende Code je nachdem, wo der Aufruf erfolgt, möglicherweise im selben Thread fortgesetzt wird, in dem er aufgerufen wurde.

Der SynchronizationContextbeim Aufruf erfasste awaitThread bestimmt, auf welchem ​​Thread die Fortsetzung ausgeführt wird.

Darüber hinaus werden bei diesem Aufruf (und dies ist ein Implementierungsdetail, das sich ändern kann) weiterhin Lese- und Schreibvorgänge ausgeführt (es wird nur kein Thread verschwendet, der beim Abschluss der E / A blockiert wird).

Ab .NET 4.0 gibt es die Stream.CopyToMethode

input.CopyTo(output);

Für .NET 3.5 und früher

Es ist nichts in das Framework eingebrannt, um dies zu unterstützen. Sie müssen den Inhalt manuell kopieren, wie folgt:

public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[32768];
    int read;
    while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write (buffer, 0, read);
    }
}

Hinweis 1: Mit dieser Methode können Sie über den Fortschritt berichten (bisher gelesene x Bytes ...).
Hinweis 2: Warum eine feste Puffergröße verwenden und nicht input.Length? Weil diese Länge möglicherweise nicht verfügbar ist! Aus den Dokumenten :

Wenn eine von Stream abgeleitete Klasse das Suchen nicht unterstützt, lösen Aufrufe von Length, SetLength, Position und Seek eine NotSupportedException aus.

Nick
quelle
58
Beachten Sie, dass dies nicht der schnellste Weg ist. Im bereitgestellten Code-Snippet müssen Sie warten, bis der Schreibvorgang abgeschlossen ist, bevor ein neuer Block gelesen wird. Beim asynchronen Lesen und Schreiben verschwindet dieses Warten. In einigen Situationen wird die Kopie dadurch doppelt so schnell. Dadurch wird der Code jedoch viel komplizierter. Wenn also Geschwindigkeit kein Problem ist, halten Sie ihn einfach und verwenden Sie diese einfache Schleife. Diese Frage auf StackOverflow hat einen Code, der das asynchrone Lesen / Schreiben veranschaulicht : stackoverflow.com/questions/1540658/… Grüße, Sebastiaan
Sebastiaan M
16
FWIW, bei meinen Tests habe ich festgestellt, dass 4096 tatsächlich schneller als 32 KB ist. Dies hängt damit zusammen, wie die CLR Chunks über eine bestimmte Größe zuweist. Aus diesem Grund verwendet die .NET-Implementierung von Stream.CopyTo anscheinend 4096.
Jeff
1
Wenn Sie wissen möchten, wie CopyToAsync implementiert ist, oder Änderungen wie ich vornehmen möchten (ich musste die maximale Anzahl der zu kopierenden Bytes angeben können), ist es als CopyStreamToStreamAsync in "Beispiele für die parallele Programmierung mit .NET Framework" code.msdn verfügbar .microsoft.com / ParExtSamples
Michael
1
FIY, optimale Puffergröße iz 81920Bytes, nicht32768
Alex Zhukovskiy
2
@ Jeff neueste referecnceSource zeigt, dass es tatsächlich 81920 Bytes Puffer verwendet.
Alex Zhukovskiy
66

MemoryStream hat .WriteTo (Outstream);

und .NET 4.0 hat .CopyTo für ein normales Stream-Objekt.

.NET 4.0:

instream.CopyTo(outstream);
Joshua
quelle
Ich sehe nicht viele Beispiele im Web, die diese Methoden verwenden. Liegt das daran, dass sie ziemlich neu sind oder gibt es einige Einschränkungen?
GeneS
3
Dies liegt daran, dass sie in .NET 4.0 neu sind. Stream.CopyTo () macht im Grunde genau das Gleiche für die Schleife wie die genehmigte Antwort, mit einigen zusätzlichen Überprüfungen der Integrität. Die Standardpuffergröße ist 4096, es gibt jedoch auch eine Überlastung, um eine größere anzugeben.
Michael Edenfield
9
Der Stream muss nach dem Kopieren zurückgespult werden: instream.Position = 0;
Draykos
6
Zusätzlich zum Zurückspulen des Eingabestreams musste der Ausgabestream zurückgespult werden: outstream.Position = 0;
JonH
32

Ich benutze die folgenden Erweiterungsmethoden. Sie haben Überladungen optimiert, wenn ein Stream ein MemoryStream ist.

    public static void CopyTo(this Stream src, Stream dest)
    {
        int size = (src.CanSeek) ? Math.Min((int)(src.Length - src.Position), 0x2000) : 0x2000;
        byte[] buffer = new byte[size];
        int n;
        do
        {
            n = src.Read(buffer, 0, buffer.Length);
            dest.Write(buffer, 0, n);
        } while (n != 0);           
    }

    public static void CopyTo(this MemoryStream src, Stream dest)
    {
        dest.Write(src.GetBuffer(), (int)src.Position, (int)(src.Length - src.Position));
    }

    public static void CopyTo(this Stream src, MemoryStream dest)
    {
        if (src.CanSeek)
        {
            int pos = (int)dest.Position;
            int length = (int)(src.Length - src.Position) + pos;
            dest.SetLength(length); 

            while(pos < length)                
                pos += src.Read(dest.GetBuffer(), pos, length - pos);
        }
        else
            src.CopyTo((Stream)dest);
    }
Eloff
quelle
1

Die grundlegenden Fragen, die Implementierungen von "CopyStream" unterscheiden, sind:

  • Größe des Lesepuffers
  • Größe der Schreibvorgänge
  • Können wir mehr als einen Thread verwenden (Schreiben während des Lesens)?

Die Antworten auf diese Fragen führen zu sehr unterschiedlichen Implementierungen von CopyStream und hängen davon ab, welche Art von Streams Sie haben und was Sie optimieren möchten. Die "beste" Implementierung müsste sogar wissen, auf welche spezifische Hardware die Streams lesen und schreiben.

Fryguybob
quelle
1
... oder die beste Implementierung könnte Überladungen aufweisen, damit Sie die Puffergröße, die Schreibgröße und die zulässigen Threads angeben können?
MarkJ
1

Es gibt tatsächlich eine weniger hartnäckige Möglichkeit, eine Stream-Kopie zu erstellen. Beachten Sie jedoch, dass dies bedeutet, dass Sie die gesamte Datei im Speicher speichern können. Versuchen Sie nicht, dies zu verwenden, wenn Sie ohne Vorsicht mit Dateien arbeiten, die Hunderte von Megabyte oder mehr umfassen.

public static void CopyStream(Stream input, Stream output)
{
  using (StreamReader reader = new StreamReader(input))
  using (StreamWriter writer = new StreamWriter(output))
  {
    writer.Write(reader.ReadToEnd());
  }
}

HINWEIS: Möglicherweise gibt es auch Probleme mit Binärdaten und Zeichencodierungen.

SmokingRope
quelle
6
Der Standardkonstruktor für StreamWriter erstellt einen UTF8-Stream ohne Stückliste ( msdn.microsoft.com/en-us/library/fysy0a4b.aspx ), sodass keine Gefahr von Codierungsproblemen besteht. Binärdaten sollten mit ziemlicher Sicherheit nicht auf diese Weise kopiert werden.
kͩeͣmͮpͥ 9
14
man könnte leicht argumentieren, dass das Laden "der gesamten Datei im Speicher" kaum als "weniger schwerfällig" angesehen wird.
Seph
Ich bekomme Outmemory Ausnahme aus diesem Grund
ColacX
Dies ist kein Stream zu Stream. reader.ReadToEnd()legt alles in RAM
Bizhan
1

.NET Framework 4 führt die neue "CopyTo" -Methode der Stream-Klasse des System.IO-Namespace ein. Mit dieser Methode können wir einen Stream in einen anderen Stream einer anderen Stream-Klasse kopieren.

Hier ist ein Beispiel dafür.

    FileStream objFileStream = File.Open(Server.MapPath("TextFile.txt"), FileMode.Open);
    Response.Write(string.Format("FileStream Content length: {0}", objFileStream.Length.ToString()));

    MemoryStream objMemoryStream = new MemoryStream();

    // Copy File Stream to Memory Stream using CopyTo method
    objFileStream.CopyTo(objMemoryStream);
    Response.Write("<br/><br/>");
    Response.Write(string.Format("MemoryStream Content length: {0}", objMemoryStream.Length.ToString()));
    Response.Write("<br/><br/>");
Jayesh Sorathia
quelle
Erinnerung: Die Verwendung CopyToAsync()wird empfohlen.
Jari Turkia
0

Leider gibt es keine wirklich einfache Lösung. Sie können so etwas versuchen:

Stream s1, s2;
byte[] buffer = new byte[4096];
int bytesRead = 0;
while (bytesRead = s1.Read(buffer, 0, buffer.Length) > 0) s2.Write(buffer, 0, bytesRead);
s1.Close(); s2.Close();

Das Problem dabei ist jedoch, dass sich die unterschiedliche Implementierung der Stream-Klasse möglicherweise anders verhält, wenn nichts zu lesen ist. Ein Stream, der eine Datei von einer lokalen Festplatte liest, wird wahrscheinlich blockiert, bis die Leseoperation genügend Daten von der Festplatte gelesen hat, um den Puffer zu füllen, und nur dann weniger Daten zurückgibt, wenn das Dateiende erreicht ist. Andererseits kann ein Stream, der aus dem Netzwerk liest, weniger Daten zurückgeben, obwohl mehr Daten zu empfangen sind.

Überprüfen Sie immer die Dokumentation der spezifischen Stream-Klasse, die Sie verwenden, bevor Sie eine generische Lösung verwenden.

Tamas Czinege
quelle
5
Die generische Lösung wird hier funktionieren - Nicks Antwort ist gut. Die Puffergröße ist natürlich eine willkürliche Wahl, aber 32 KB klingen vernünftig. Ich denke, Nicks Lösung ist richtig, die Streams nicht zu schließen - überlassen Sie das dem Besitzer.
Jon Skeet
0

Je nachdem, mit welcher Art von Stream Sie arbeiten, kann dies effizienter durchgeführt werden. Wenn Sie einen oder beide Ihrer Streams in einen MemoryStream konvertieren können, können Sie die GetBuffer-Methode verwenden, um direkt mit einem Byte-Array zu arbeiten, das Ihre Daten darstellt. Auf diese Weise können Sie Methoden wie Array.CopyTo verwenden, die alle von fryguybob aufgeworfenen Probleme abstrahieren. Sie können .NET einfach vertrauen, um die optimale Methode zum Kopieren der Daten zu kennen.

Codierer
quelle
0

Wenn Sie möchten, dass ein Prozess einen Stream in einen anderen kopiert, der von Nick gepostet wurde, ist dies in Ordnung, aber es fehlt das Zurücksetzen der Position

public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[32768];
    long TempPos = input.Position;
    while (true)    
    {
        int read = input.Read (buffer, 0, buffer.Length);
        if (read <= 0)
            return;
        output.Write (buffer, 0, read);
    }
    input.Position = TempPos;// or you make Position = 0 to set it at the start
}

Wenn es jedoch zur Laufzeit keine Prozedur verwendet, sollten Sie einen Speicherstrom verwenden

Stream output = new MemoryStream();
byte[] buffer = new byte[32768]; // or you specify the size you want of your buffer
long TempPos = input.Position;
while (true)    
{
    int read = input.Read (buffer, 0, buffer.Length);
    if (read <= 0)
        return;
    output.Write (buffer, 0, read);
 }
    input.Position = TempPos;// or you make Position = 0 to set it at the start
Kronass
quelle
3
Sie sollten die Position des Eingabestreams nicht ändern, da nicht alle Streams einen wahlfreien Zugriff ermöglichen. In einem Netzwerkstrom können Sie beispielsweise die Position nicht ändern, sondern nur lesen und / oder schreiben.
R. Martinho Fernandes
0

Da keine der Antworten eine asynchrone Methode zum Kopieren von einem Stream in einen anderen behandelt hat, ist hier ein Muster, das ich erfolgreich in einer Portweiterleitungsanwendung verwendet habe, um Daten von einem Netzwerkstrom in einen anderen zu kopieren. Es fehlt die Ausnahmebehandlung, um das Muster hervorzuheben.

const int BUFFER_SIZE = 4096;

static byte[] bufferForRead = new byte[BUFFER_SIZE];
static byte[] bufferForWrite = new byte[BUFFER_SIZE];

static Stream sourceStream = new MemoryStream();
static Stream destinationStream = new MemoryStream();

static void Main(string[] args)
{
    // Initial read from source stream
    sourceStream.BeginRead(bufferForRead, 0, BUFFER_SIZE, BeginReadCallback, null);
}

private static void BeginReadCallback(IAsyncResult asyncRes)
{
    // Finish reading from source stream
    int bytesRead = sourceStream.EndRead(asyncRes);
    // Make a copy of the buffer as we'll start another read immediately
    Array.Copy(bufferForRead, 0, bufferForWrite, 0, bytesRead);
    // Write copied buffer to destination stream
    destinationStream.BeginWrite(bufferForWrite, 0, bytesRead, BeginWriteCallback, null);
    // Start the next read (looks like async recursion I guess)
    sourceStream.BeginRead(bufferForRead, 0, BUFFER_SIZE, BeginReadCallback, null);
}

private static void BeginWriteCallback(IAsyncResult asyncRes)
{
    // Finish writing to destination stream
    destinationStream.EndWrite(asyncRes);
}
mdonatas
quelle
4
Wenn der zweite Lesevorgang vor dem ersten Schreibvorgang abgeschlossen ist, werden Sie den Inhalt von bufferForWrite vom ersten Lesevorgang an überschreiben, bevor er ausgeschrieben wird.
Peter Jeffery
0

Für .NET 3.5 und bevor Sie versuchen:

MemoryStream1.WriteTo(MemoryStream2);
ntiago
quelle
Dies funktioniert jedoch nur, wenn Sie mit MemoryStreams arbeiten.
Nyerguds
0

Einfach und sicher - erstellen Sie einen neuen Stream aus der Originalquelle:

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);
Graham Laight
quelle