Question

Quel est le meilleur moyen de copier le contenu d'un flux sur un autre? Existe-t-il une méthode d’utilité standard pour cela?

Était-ce utile?

La solution

À partir de .NET 4.5, il existe le Méthode Stream.CopyToAsync

input.CopyToAsync(output);

Ceci renverra une Tâche pouvant être poursuivi une fois terminé, comme suit:

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 ci-dessous peut continuer ou non sur le même fil que celui qui l'a appelé.

Le SynchronizationContext qui a été capturé lors de l'appel à wait déterminera le fil sur lequel la suite sera exécutée.

De plus, cet appel (et il s'agit d'un détail d'implémentation susceptible de changer) continue de lire et d'écrire en séquence (il ne gaspille tout simplement pas un blocage de threads à l'achèvement des E / S).

À partir de .NET 4.0, vous trouverez le Méthode Stream.CopyTo

input.CopyTo(output);

Pour .NET 3.5 et versions antérieure

Il n’ya rien dans le cadre pour aider à cela; vous devez copier le contenu manuellement, comme suit:

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 ...)
Remarque 2: Pourquoi utiliser une taille de tampon fixe et non input.Length ? Parce que cette longueur peut ne pas être disponible! docs :

  

Si une classe dérivée de Stream ne prend pas en charge la recherche, les appels à Length, SetLength, Position et Seek émettent une exception NotSupportedException.

Autres conseils

MemoryStream a .WriteTo (en sortie);

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 & Copy; StreamStream " sont:

  • taille du tampon de lecture
  • taille des écritures
  • Pouvons-nous utiliser plus d'un fil (écriture pendant la lecture)?

Les réponses à ces questions conduisent à des implémentations très différentes de CopyStream et dépendent du type de flux que vous avez et de ce que vous essayez d'optimiser. Le "meilleur" L’implémentation aurait même besoin de savoir à quel matériel les flux étaient lus et écrits.

Il existe en fait une manière moins lourde de faire une copie de flux. Notez cependant que cela implique que vous pouvez stocker le fichier entier en mémoire. N'essayez pas de l'utiliser si vous travaillez avec des fichiers allant jusqu'à 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 codages de caractères.

.NET Framework 4 introduit un nouveau " CopyTo " méthode de la classe de flux de l'espace de noms System.IO. En utilisant cette méthode, nous pouvons copier un flux dans 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/>");

Malheureusement, il n’existe 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 de cette implémentation différente de la classe Stream pourrait se comporter différemment s’il n’ya rien à lire. Un flux qui lit un fichier sur un disque dur local va probablement se bloquer jusqu'à ce que l'opération de lecture ait lu suffisamment de données sur le disque pour remplir le tampon et ne renvoyer que moins de données s'il atteint la fin du fichier. D'autre part, un flux de lecture du 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 existe peut-être un moyen de le faire plus efficacement, en fonction du type de flux avec lequel vous travaillez. Si vous pouvez convertir un ou deux flux 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 telles que Array.CopyTo, qui résument toutes les questions soulevées par fryguybob. Vous pouvez simplement faire confiance à .NET pour connaître le moyen optimal de copier les données.

si vous voulez qu'une procédure copie un flux vers un autre flux que nick a posté est correct mais qu'il manque la réinitialisation de la 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 si, au moment de l'exécution, n'utilisez pas de procédure, vous devriez utiliser un 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

Etant donné qu'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 transfert de port pour copier des données d'un flux de réseau à un autre. Il n’ya pas de 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);
}

Pour .NET 3.5 et avant d'essayer:

MemoryStream1.WriteTo(MemoryStream2);

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);
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top