Quelle est la meilleure façon de copier le contenu d'un flux vers un autre? Existe-t-il une méthode d'utilité standard pour cela?
Quelle est la meilleure façon de copier le contenu d'un flux vers un autre? Existe-t-il une méthode d'utilité standard pour cela?
Réponses:
À partir de .NET 4.5, il existe la Stream.CopyToAsync
méthode
input.CopyToAsync(output);
Cela renverra un Task
qui peut être poursuivi une fois terminé, comme ceci:
await input.CopyToAsync(output)
// Code from here on will be run in a continuation.
Notez que selon l'endroit où l'appel CopyToAsync
est effectué, le code qui suit peut ou non continuer sur le même thread qui l'a appelé.
Celui SynchronizationContext
qui a été capturé lors de l'appel await
déterminera sur quel thread la continuation sera exécutée.
De plus, cet appel (et il s'agit d'un détail d'implémentation susceptible d'être modifié) encode toujours les lectures et les écritures (il ne gaspille tout simplement pas un blocage des threads à la fin des E / S).
À partir de .NET 4.0, il y a la Stream.CopyTo
méthode
input.CopyTo(output);
Pour .NET 3.5 et versions antérieures
Il n'y a rien de prévu dans le cadre pour aider à cela; vous devez copier le contenu manuellement, comme ceci:
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);
}
}
Note 1: Cette méthode vous permettra de rendre compte de l'avancement (x octets lus jusqu'à présent ...)
Note 2: Pourquoi utiliser une taille de tampon fixe et non input.Length
? Parce que cette longueur peut ne pas être disponible! De la documentation :
Si une classe dérivée de Stream ne prend pas en charge la recherche, les appels à Length, SetLength, Position et Seek lèvent une exception NotSupportedException.
81920
octets, pas32768
MemoryStream a .WriteTo (outstream);
et .NET 4.0 a .CopyTo sur un objet de flux normal.
.NET 4.0:
instream.CopyTo(outstream);
J'utilise les méthodes d'extension suivantes. Ils ont optimisé les surcharges lorsqu'un flux est 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);
}
Les questions de base qui différencient les implémentations de "CopyStream" sont:
Les réponses à ces questions se traduisent par des implémentations très différentes de CopyStream et dépendent du type de flux dont vous disposez et de ce que vous essayez d'optimiser. La «meilleure» implémentation aurait même besoin de savoir sur quel matériel spécifique les flux lisaient et écrivaient.
Il existe en fait une façon moins lourde de faire une copie de flux. Notez cependant que cela implique que vous pouvez stocker l'intégralité du fichier en mémoire. N'essayez pas de l'utiliser si vous travaillez avec des fichiers de plusieurs centaines de mégaoctets ou plus, sans précaution.
public static void CopyStream(Stream input, Stream output)
{
using (StreamReader reader = new StreamReader(input))
using (StreamWriter writer = new StreamWriter(output))
{
writer.Write(reader.ReadToEnd());
}
}
REMARQUE: il peut également y avoir des problèmes concernant les données binaires et les encodages de caractères.
reader.ReadToEnd()
met tout en RAM
.NET Framework 4 présente la nouvelle méthode "CopyTo" de l'espace de noms Stream Class of System.IO. En utilisant cette méthode, nous pouvons copier un flux vers un autre flux de classe de flux différente.
Voici un exemple pour cela.
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()
est encouragée.
Malheureusement, il n'y a pas de solution vraiment simple. Vous pouvez essayer quelque chose comme ça:
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();
Mais le problème avec cette implémentation différente de la classe Stream peut se comporter différemment s'il n'y a rien à lire. Un flux lisant un fichier à partir d'un disque dur local se bloquera probablement jusqu'à ce que l'opération de lecture ait lu suffisamment de données du disque pour remplir le tampon et ne renvoie moins de données que si elle atteint la fin du fichier. D'un autre côté, une lecture de flux depuis le réseau peut renvoyer moins de données même s'il reste plus de données à recevoir.
Vérifiez toujours la documentation de la classe de flux spécifique que vous utilisez avant d'utiliser une solution générique.
Il peut y avoir un moyen de le faire plus efficacement, selon le type de flux avec lequel vous travaillez. Si vous pouvez convertir l'un de vos flux ou les deux en un MemoryStream, vous pouvez utiliser la méthode GetBuffer pour travailler directement avec un tableau d'octets représentant vos données. Cela vous permet d'utiliser des méthodes comme Array.CopyTo, qui résument tous les problèmes soulevés par fryguybob. Vous pouvez simplement faire confiance à .NET pour connaître la façon optimale de copier les données.
si vous voulez qu'un procdure copie un flux vers un autre que celui que nick a posté est correct mais qu'il manque la réinitialisation de position, il devrait être
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
}
mais s'il est en cours d'exécution n'utilisant pas une procédure, vous devez utiliser le flux de mémoire
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
Puisqu'aucune des réponses n'a couvert une manière asynchrone de copier d'un flux à un autre, voici un modèle que j'ai utilisé avec succès dans une application de redirection de port pour copier des données d'un flux réseau à un autre. Il manque une gestion des exceptions pour souligner le motif.
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 et sûr - créez un nouveau flux à partir de la source d'origine:
MemoryStream source = new MemoryStream(byteArray);
MemoryStream copy = new MemoryStream(byteArray);