web-dev-qa-db-fra.com

Comment copier le contenu d'un flux sur un autre?

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?

504
Anton

À partir de .NET 4.5, il existe la méthode Stream.CopyToAsync

_input.CopyToAsync(output);
_

Ceci retournera un Task qui peut ê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 thread qui l'a appelé.

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

De plus, cet appel (et il s'agit d'un détail d'implémentation susceptible d'être modifié) continue à séquencer les lectures et les écritures (il ne gaspille tout simplement pas un blocage de threads à l'achèvement des E/S).

À partir de .NET 4.0, il y a la méthode Stream.CopyTo

_input.CopyTo(output);
_

Pour .NET 3.5 et avant

Il n'y a 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 ...)
Note 2: Pourquoi utiliser une taille de tampon fixe et non pas _input.Length_? Parce que cette longueur peut ne pas être disponible! De la 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.

678
Nick

MemoryStream a .WriteTo (en aval);

et .NET 4.0 a .CopyTo sur un objet de flux normal.

.NET 4.0:

instream.CopyTo(outstream);
64
Joshua

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);
    }
31
Eloff

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'à des 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());
  }
}

NOTE: Il peut aussi y avoir des problèmes concernant les données binaires et les encodages de caractères.

2
hannasm

.NET Framework 4 introduit la nouvelle méthode "CopyTo" de Stream Class 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/>");
1
Jayesh Sorathia

Les questions de base qui différencient les implémentations de "CopyStream" sont les suivantes:

  • taille du tampon de lecture
  • taille de l'écrit
  • Pouvons-nous utiliser plus d’un fil de discussion (écrire en cours de 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. La "meilleure" implémentation aurait même besoin de savoir à quel matériel les flux étaient lus et écrits.

1
fryguybob

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 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 sur 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.

0
Tamas Czinege

si vous voulez qu'un processus copie un flux sur un autre fichier que Nick a posté est correct mais qu'il manque la réinitialisation de la position, il devrait l'ê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 de procédure, vous devez 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
0
Kronass

Étant donné qu'aucune des réponses n'a couvert une façon 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 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);
}
0
mdonatas

Pour .NET 3.5 et avant d'essayer:

MemoryStream1.WriteTo(MemoryStream2);
0
ntiago

Facile et sûr - créez un nouveau flux à partir de la source originale:

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

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 l'un ou l'autre de vos flux en 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.

0
Coderer