¿Cómo copio el contenido de una transmisión a otra?

521

¿Cuál es la mejor manera de copiar el contenido de una secuencia a otra? ¿Existe un método de utilidad estándar para esto?

Anton
fuente
Quizás lo más importante en este punto, ¿cómo se copia el contenido "de manera fluida", lo que significa que solo copia la secuencia de origen ya que algo consume la secuencia de destino ...?
drzaus

Respuestas:

694

Desde .NET 4.5 en adelante, existe el Stream.CopyToAsyncmétodo

input.CopyToAsync(output);

Esto devolverá un mensaje Taskque puede continuar cuando se complete, de esta manera:

await input.CopyToAsync(output)

// Code from here on will be run in a continuation.

Tenga en cuenta que dependiendo de dónde CopyToAsyncse realice la llamada , el código que sigue puede o no continuar en el mismo hilo que lo llamó.

Lo SynchronizationContextque se capturó al llamar awaitdeterminará en qué hilo se ejecutará la continuación.

Además, esta llamada (y este es un detalle de implementación sujeto a cambios) sigue secuenciando lecturas y escrituras (simplemente no desperdicia un bloqueo de subprocesos al finalizar la E / S).

A partir de .NET 4.0, existe el Stream.CopyTométodo

input.CopyTo(output);

Para .NET 3.5 y anteriores

No hay nada integrado en el marco para ayudar con esto; tienes que copiar el contenido manualmente, así:

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: Este método le permitirá informar sobre el progreso (x bytes leídos hasta ahora ...)
Nota 2: ¿Por qué usar un tamaño de búfer fijo y no input.Length? ¡Porque esa longitud puede no estar disponible! De los documentos :

Si una clase derivada de Stream no admite búsquedas, las llamadas a Length, SetLength, Position y Seek arrojan una excepción NotSupportedException.

Nick
fuente
58
Tenga en cuenta que esta no es la forma más rápida de hacerlo. En el fragmento de código proporcionado, debe esperar a que se complete la escritura antes de leer un nuevo bloque. Al hacer la lectura y escritura de forma asincrónica, esta espera desaparecerá. En alguna situación, esto hará que la copia sea el doble de rápido. Sin embargo, hará que el código sea mucho más complicado, por lo que si la velocidad no es un problema, manténgalo simple y use este ciclo simple. Esta pregunta en StackOverflow tiene un código que ilustra la lectura / escritura asíncrona: stackoverflow.com/questions/1540658/… Saludos, Sebastiaan
Sebastiaan M
16
FWIW, en mis pruebas descubrí que 4096 es en realidad más rápido que 32K. Algo relacionado con la forma en que el CLR asigna fragmentos en un determinado tamaño. Debido a esto, la implementación .NET de Stream.CopyTo aparentemente usa 4096.
Jeff
1
Si desea saber cómo se implementa CopyToAsync o hacer modificaciones como lo hice (necesitaba poder especificar el número máximo de bytes para copiar), entonces está disponible como CopyStreamToStreamAsync en "Muestras para programación paralela con el código
Michael
1
FIY, tamaño de búfer óptimo iz 81920bytes, no32768
Alex Zhukovskiy
2
@Jeff last referecnceSource muestra que en realidad usa un búfer de 81920 bytes.
Alex Zhukovskiy
66

MemoryStream tiene .WriteTo (outstream);

y .NET 4.0 tiene .CopyTo en el objeto de flujo normal.

.NET 4.0:

instream.CopyTo(outstream);
Joshua
fuente
No veo muchas muestras en la web usando estos métodos. ¿Es esto porque son bastante nuevos o hay algunas limitaciones?
GeneS
3
Es porque son nuevos en .NET 4.0. Stream.CopyTo () básicamente hace exactamente lo mismo para el bucle que la respuesta aprobada, con algunas comprobaciones de cordura adicionales. El tamaño predeterminado del búfer es 4096, pero también hay una sobrecarga para especificar uno más grande.
Michael Edenfield
99
La secuencia debe rebobinarse después de la copia: instream.Position = 0;
Draykos
66
Además de rebobinar el flujo de entrada, también encontré la necesidad de rebobinar el flujo de salida: outstream.Position = 0;
JonH
32

Yo uso los siguientes métodos de extensión. Han optimizado las sobrecargas para cuando una secuencia es 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);
    }
Eloff
fuente
1

Las preguntas básicas que diferencian las implementaciones de "CopyStream" son:

  • tamaño del búfer de lectura
  • tamaño de las escrituras
  • ¿Podemos usar más de un hilo (escribir mientras leemos)?

Las respuestas a estas preguntas dan como resultado implementaciones muy diferentes de CopyStream y dependen de qué tipo de transmisiones tiene y qué está tratando de optimizar. La "mejor" implementación incluso necesitaría saber en qué hardware específico estaban leyendo y escribiendo las transmisiones.

fryguybob
fuente
1
... o la mejor implementación podría tener sobrecargas para permitirle especificar el tamaño del búfer, el tamaño de escritura y si los subprocesos están permitidos?
MarkJ
1

En realidad, hay una forma menos dura de hacer una copia de la transmisión. Sin embargo, tenga en cuenta que esto implica que puede almacenar todo el archivo en la memoria. No intente usar esto si está trabajando con archivos que van a cientos de megabytes o más, sin precaución.

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: También puede haber algunos problemas relacionados con los datos binarios y las codificaciones de caracteres.

Fumar Cuerda
fuente
66
El constructor predeterminado para StreamWriter crea una secuencia UTF8 sin una lista de materiales ( msdn.microsoft.com/en-us/library/fysy0a4b.aspx ), por lo que no hay peligro de problemas de codificación. Es casi seguro que los datos binarios no deberían copiarse de esta manera.
kͩeͣmͮpͥ ͩ
14
se podría argumentar fácilmente que cargar "todo el archivo en la memoria" difícilmente se considera "menos pesado".
Seph
me sale excepción outmemory debido a esto
ColacX
Esto no es transmisión a transmisión. reader.ReadToEnd()pone todo en RAM
Bizhan
1

.NET Framework 4 presenta el nuevo método "CopyTo" del espacio de nombres Stream Class of System.IO. Usando este método podemos copiar una secuencia a otra secuencia de diferente clase de secuencia.

Aquí hay un ejemplo de esto.

    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/>");
Jayesh Sorathia
fuente
Recordatorio: CopyToAsync()se recomienda el uso.
Jari Turkia
0

Desafortunadamente, no hay una solución realmente simple. Puedes probar algo así:

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();

Pero el problema con eso es que una implementación diferente de la clase Stream podría comportarse de manera diferente si no hay nada que leer. Una secuencia que lee un archivo de un disco duro local probablemente se bloqueará hasta que la operación de lectura haya leído suficientes datos del disco para llenar el búfer y solo devolverá menos datos si llega al final del archivo. Por otro lado, una lectura de flujo de la red podría devolver menos datos a pesar de que quedan más datos por recibir.

Siempre verifique la documentación de la clase de transmisión específica que está utilizando antes de usar una solución genérica.

Tamas Czinege
fuente
55
La solución genérica funcionará aquí: la respuesta de Nick es buena. El tamaño del buffer es una elección arbitraria, por supuesto, pero 32K suena razonable. Sin embargo, creo que la solución de Nick es la correcta para no cerrar las transmisiones; déjelo al propietario.
Jon Skeet
0

Puede haber una manera de hacer esto de manera más eficiente, dependiendo del tipo de transmisión con la que esté trabajando. Si puede convertir una o ambas transmisiones en un MemoryStream, puede usar el método GetBuffer para trabajar directamente con una matriz de bytes que represente sus datos. Esto le permite utilizar métodos como Array.CopyTo, que resumen todos los problemas planteados por fryguybob. Puede confiar en .NET para conocer la forma óptima de copiar los datos.

Coderer
fuente
0

si desea que un procedimiento copie una transmisión a otra que el nick publicado está bien pero le falta el restablecimiento de posición, debe ser

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
}

pero si está en tiempo de ejecución sin usar un procedimiento, debe usar el flujo de 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
Kronass
fuente
3
No debe cambiar la posición de la secuencia de entrada, porque no todas las secuencias permiten el acceso aleatorio. En una secuencia de red, por ejemplo, no puede cambiar la posición, solo leer y / o escribir.
R. Martinho Fernandes
0

Dado que ninguna de las respuestas ha cubierto una forma asincrónica de copiar de una secuencia a otra, aquí hay un patrón que he utilizado con éxito en una aplicación de reenvío de puertos para copiar datos de una secuencia de red a otra. Carece de manejo de excepciones para enfatizar el patrón.

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);
}
mdonatas
fuente
44
Seguramente si la segunda lectura se completa antes de la primera escritura, entonces escribirá sobre el contenido de bufferForWrite de la primera lectura, antes de que se escriba.
Peter Jeffery
0

Para .NET 3.5 y antes de probar:

MemoryStream1.WriteTo(MemoryStream2);
ntiago
fuente
Sin embargo, eso solo funciona si se trata de MemoryStreams.
Nyerguds
0

Fácil y seguro: haga una nueva transmisión desde la fuente original:

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