c# - stream copyto
¿Cómo copio los contenidos de una transmisión a otra? (12)
¿Cuál es la mejor manera de copiar el contenido de un flujo a otro? ¿Hay un método de utilidad estándar para esto?
.NET Framework 4 introduce el nuevo método "CopyTo" de Stream Class del espacio de nombres System.IO. Usando este método, podemos copiar una secuencia a otra secuencia de diferente clase de secuencia.
Aquí hay un ejemplo para 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/>");
Como ninguna de las respuestas ha cubierto una forma asíncrona de copiar de una secuencia a otra, aquí hay un patrón que he usado con éxito en una aplicación de reenvío de puertos para copiar datos de una secuencia de red a otra. Carece de manejo excepcional 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);
}
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 la implementación diferente de la clase Stream puede comportarse de manera diferente si no hay nada que leer. Una secuencia que lea un archivo desde 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 devuelva menos datos si llega al final del archivo. Por otro lado, una lectura de la transmisión desde la red puede devolver menos datos, aunque aún 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.
En realidad, hay una forma menos dura de hacer una copia de flujo. Tenga en cuenta, sin embargo, que esto implica que puede almacenar todo el archivo en la memoria. No intente usar esto si está trabajando con archivos de 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.
Fácil y seguro: crea una nueva secuencia a partir de la fuente original:
MemoryStream source = new MemoryStream(byteArray);
MemoryStream copy = new MemoryStream(byteArray);
Las preguntas básicas que diferencian las implementaciones de "CopyStream" son:
- tamaño del buffer de lectura
- tamaño de las escrituras
- ¿Podemos usar más de un hilo (escribiendo mientras estamos leyendo)?
Las respuestas a estas preguntas dan como resultado implementaciones muy diferentes de CopyStream y dependen de qué tipo de secuencias 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 secuencias.
MemoryStream tiene .WriteTo (outstream);
y .NET 4.0 tiene .CopyTo en el objeto de secuencia normal.
.NET 4.0:
instream.CopyTo(outstream);
Para .NET 3.5 y antes intente:
MemoryStream1.WriteTo(MemoryStream2);
Puede haber una manera de hacerlo de manera más eficiente, dependiendo del tipo de flujo con el que esté trabajando. Si puede convertir uno o ambos de sus flujos a un MemoryStream, puede usar el método GetBuffer para trabajar directamente con una matriz de bytes que representa sus datos. Esto le permite utilizar métodos como Array.CopyTo, que abstrae todos los problemas planteados por fryguybob. Solo puede confiar en .NET para conocer la manera óptima de copiar los datos.
Si desea un procedimiento para copiar una transmisión a otra, la que Nick publicó está bien, pero le falta el restablecimiento de la posición, debería 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 utilizar un procedimiento, debe utilizar 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
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);
}
A partir de .NET 4.5, está el método Stream.CopyToAsync
input.CopyToAsync(output);
Esto devolverá una Task
que puede continuar cuando se complete, así:
await input.CopyToAsync(output)
// Code from here on will be run in a continuation.
Tenga en cuenta que dependiendo de dónde se CopyToAsync
la llamada a CopyToAsync
, el código que sigue puede o no continuar en el mismo hilo que lo llamó.
El SynchronizationContext
que se capturó cuando se await
llamada determinará en qué hilo se ejecutará la continuación.
Además, esta llamada (y este es un detalle de implementación sujeto a cambio) todavía secuencia las lecturas y las escrituras (simplemente no desperdicia un bloqueo de subprocesos en la finalización de E / S).
A partir de .NET 4.0, está el método Stream.CopyTo
input.CopyTo(output);
Para .NET 3.5 y antes
No hay nada cocido 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 la docs :
Si una clase derivada de Stream no admite la búsqueda, las llamadas a Length, SetLength, Position y Seek arrojan una excepción NotSupportedException.