Qual è il modo migliore per copiare i contenuti di uno stream su un altro? Esiste un metodo di utilità standard per questo?
Qual è il modo migliore per copiare i contenuti di uno stream su un altro? Esiste un metodo di utilità standard per questo?
Risposte:
Da .NET 4.5 in poi, c'è il Stream.CopyToAsync
metodo
input.CopyToAsync(output);
Questo restituirà un Task
che può essere continuato quando completato, in questo modo:
await input.CopyToAsync(output)
// Code from here on will be run in a continuation.
Si noti che, a seconda di dove CopyToAsync
viene effettuata la chiamata , il codice che segue potrebbe continuare o meno sullo stesso thread che l'ha chiamato.
Quello SynchronizationContext
che è stato catturato durante la chiamata await
determinerà su quale thread verrà eseguita la continuazione.
Inoltre, questa chiamata (e questo è un dettaglio dell'implementazione soggetto a modifiche) continua a leggere e scrivere sequenze (semplicemente non spreca un blocco di thread al completamento dell'I / O).
Da .NET 4.0 in poi, c'è il Stream.CopyTo
metodo
input.CopyTo(output);
Per .NET 3.5 e precedenti
Non c'è nulla di cotto nel framework per aiutare con questo; devi copiare il contenuto manualmente, in questo modo:
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);
}
}
Nota 1: questo metodo consente di generare rapporti sullo stato di avanzamento (x byte letti finora ...)
Nota 2: Perché utilizzare una dimensione del buffer fissa e non input.Length
? Perché quella lunghezza potrebbe non essere disponibile! Dai documenti :
Se una classe derivata da Stream non supporta la ricerca, le chiamate a Length, SetLength, Position e Seek generano NotSupportedException.
81920
byte, non32768
MemoryStream ha .WriteTo (a monte);
e .NET 4.0 ha .CopyTo sul normale oggetto stream.
.NET 4.0:
instream.CopyTo(outstream);
Uso i seguenti metodi di estensione. Hanno sovraccarichi ottimizzati per quando un flusso è un MemoryStream.
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);
}
Le domande di base che differenziano le implementazioni di "CopyStream" sono:
Le risposte a queste domande comportano implementazioni molto diverse di CopyStream e dipendono dal tipo di stream che hai e da ciò che stai cercando di ottimizzare. L'implementazione "migliore" avrebbe anche bisogno di sapere su quale hardware specifico i flussi stessero leggendo e scrivendo.
In realtà, esiste un modo meno pesante di fare una copia stream. Si noti tuttavia che ciò implica che è possibile memorizzare l'intero file in memoria. Non provare a usarlo se stai lavorando con file che vanno a centinaia di megabyte o più, senza cautela.
public static void CopyStream(Stream input, Stream output)
{
using (StreamReader reader = new StreamReader(input))
using (StreamWriter writer = new StreamWriter(output))
{
writer.Write(reader.ReadToEnd());
}
}
NOTA: potrebbero esserci anche alcuni problemi relativi ai dati binari e alla codifica dei caratteri.
reader.ReadToEnd()
mette tutto in RAM
.NET Framework 4 introduce il nuovo metodo "CopyTo" della classe Stream dello spazio dei nomi System.IO. Utilizzando questo metodo possiamo copiare un flusso in un altro flusso di diversa classe di flusso.
Ecco un esempio per questo.
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()
è incoraggiato.
Sfortunatamente, non esiste una soluzione davvero semplice. Puoi provare qualcosa del genere:
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();
Ma il problema è che quella diversa implementazione della classe Stream potrebbe comportarsi diversamente se non c'è nulla da leggere. Un flusso che legge un file da un disco rigido locale probabilmente si bloccherà fino a quando l'operazione di lettura non avrà letto abbastanza dati dal disco per riempire il buffer e restituire meno dati solo se raggiunge la fine del file. D'altra parte, un flusso che legge dalla rete potrebbe restituire meno dati anche se sono rimasti più dati da ricevere.
Controllare sempre la documentazione della classe di flusso specifica che si sta utilizzando prima di utilizzare una soluzione generica.
Potrebbe esserci un modo per farlo in modo più efficiente, a seconda del tipo di flusso con cui stai lavorando. Se puoi convertire uno o entrambi i tuoi stream in MemoryStream, puoi utilizzare il metodo GetBuffer per lavorare direttamente con un array di byte che rappresenta i tuoi dati. Ciò ti consente di utilizzare metodi come Array.CopyTo, che astraggono tutti i problemi sollevati da fryguybob. Puoi semplicemente fidarti di .NET per conoscere il modo ottimale di copiare i dati.
se vuoi che un procdure copi un flusso su un altro che quello pubblicato da nick va bene ma manca il reset della posizione, dovrebbe essere
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
}
ma se è in fase di runtime che non utilizza una procedura, è necessario utilizzare il flusso di memoria
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
Poiché nessuna delle risposte ha coperto un modo asincrono di copiare da uno stream a un altro, ecco uno schema che ho usato con successo in un'applicazione port forwarding per copiare dati da uno stream di rete a un altro. Manca la gestione delle eccezioni per enfatizzare il modello.
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);
}
Facile e sicuro: crea un nuovo stream dalla fonte originale:
MemoryStream source = new MemoryStream(byteArray);
MemoryStream copy = new MemoryStream(byteArray);