Was ist der beste Weg, um den Inhalt eines Streams in einen anderen zu kopieren? Gibt es dafür eine Standarddienstprogrammmethode?
Was ist der beste Weg, um den Inhalt eines Streams in einen anderen zu kopieren? Gibt es dafür eine Standarddienstprogrammmethode?
Antworten:
Ab .NET 4.5 gibt es die Stream.CopyToAsync
Methode
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, CopyToAsync
dass der folgende Code je nachdem, wo der Aufruf erfolgt, möglicherweise im selben Thread fortgesetzt wird, in dem er aufgerufen wurde.
Der SynchronizationContext
beim Aufruf erfasste await
Thread 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.CopyTo
Methode
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.
81920
Bytes, nicht32768
MemoryStream hat .WriteTo (Outstream);
und .NET 4.0 hat .CopyTo für ein normales Stream-Objekt.
.NET 4.0:
instream.CopyTo(outstream);
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);
}
Die grundlegenden Fragen, die Implementierungen von "CopyStream" unterscheiden, sind:
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.
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.
reader.ReadToEnd()
legt alles in RAM
.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/>");
CopyToAsync()
wird empfohlen.
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.
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.
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
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);
}
Einfach und sicher - erstellen Sie einen neuen Stream aus der Originalquelle:
MemoryStream source = new MemoryStream(byteArray);
MemoryStream copy = new MemoryStream(byteArray);