Qual é a melhor maneira de copiar o conteúdo de um fluxo para outro? Existe um método utilitário padrão para isso?
Qual é a melhor maneira de copiar o conteúdo de um fluxo para outro? Existe um método utilitário padrão para isso?
Respostas:
A partir do .NET 4.5, existe o Stream.CopyToAsync
método
input.CopyToAsync(output);
Isso retornará um Task
que pode ser continuado quando concluído, da seguinte forma:
await input.CopyToAsync(output)
// Code from here on will be run in a continuation.
Observe que, dependendo de onde a chamada CopyToAsync
é feita, o código a seguir pode ou não continuar no mesmo thread que a chamou.
O SynchronizationContext
que foi capturado ao chamar await
determinará em qual thread a continuação será executada.
Além disso, essa chamada (e este é um detalhe da implementação sujeito a alterações) ainda sequencia as leituras e gravações (ela simplesmente não desperdiça um encadeamento de bloqueios na conclusão de E / S).
A partir do .NET 4.0, existe o Stream.CopyTo
método
input.CopyTo(output);
Para .NET 3.5 e anteriores
Não há nada incorporado à estrutura para ajudar nisso; você precisa copiar o conteúdo manualmente, da seguinte forma:
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 permitirá que você relate sobre o progresso (x bytes lidos até agora ...)
Nota 2: Por que usar um tamanho de buffer fixo e não input.Length
? Porque esse comprimento pode não estar disponível! Dos documentos :
Se uma classe derivada de Stream não suportar busca, as chamadas Length, SetLength, Position e Seek lançam uma NotSupportedException.
81920
bytes, não32768
MemoryStream possui .WriteTo (outstream);
e o .NET 4.0 possui .CopyTo no objeto de fluxo normal.
.NET 4.0:
instream.CopyTo(outstream);
Eu uso os seguintes métodos de extensão. Eles otimizaram sobrecargas para quando um fluxo é um 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);
}
As perguntas básicas que diferenciam as implementações do "CopyStream" são:
As respostas a essas perguntas resultam em implementações muito diferentes do CopyStream e dependem do tipo de fluxo que você tem e do que está tentando otimizar. A "melhor" implementação precisaria até saber em qual hardware específico os fluxos estavam lendo e gravando.
Na verdade, existe uma maneira menos pesada de fazer uma cópia de fluxo. Observe, no entanto, que isso implica que você pode armazenar o arquivo inteiro na memória. Não tente usá-lo se estiver trabalhando com arquivos com centenas de megabytes ou mais, sem cautela.
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: Também pode haver alguns problemas relacionados a dados binários e codificações de caracteres.
reader.ReadToEnd()
coloca tudo na RAM
O .NET Framework 4 apresenta o novo método "CopyTo" do espaço para nome Stream Class of System.IO. Usando esse método, podemos copiar um fluxo para outro fluxo de classe de fluxo diferente.
Aqui está um exemplo disso.
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()
é incentivado.
Infelizmente, não existe uma solução realmente simples. Você pode tentar algo assim:
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();
Mas o problema é que essa implementação diferente da classe Stream pode se comportar de maneira diferente se não houver nada para ler. Um fluxo que lê um arquivo de um disco rígido local provavelmente bloqueará até que a operação de leitura tenha lido dados suficientes do disco para preencher o buffer e retornará apenas menos dados se chegar ao final do arquivo. Por outro lado, uma leitura de fluxo da rede pode retornar menos dados, embora ainda haja mais dados a serem recebidos.
Sempre verifique a documentação da classe de fluxo específica que você está usando antes de usar uma solução genérica.
Pode haver uma maneira de fazer isso de forma mais eficiente, dependendo do tipo de fluxo com o qual você está trabalhando. Se você pode converter um ou os dois fluxos em um MemoryStream, pode usar o método GetBuffer para trabalhar diretamente com uma matriz de bytes que representa seus dados. Isso permite que você use métodos como Array.CopyTo, que abstraem todos os problemas levantados pelo fryguybob. Você pode confiar no .NET para saber a maneira ideal de copiar os dados.
se você deseja que um procedimento copie um fluxo para outro que nick postou, mas está faltando a redefinição de posição, deve 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
}
mas se estiver em tempo de execução não usando um procedimento, você deverá usar o fluxo de memória
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
Como nenhuma das respostas abordou uma maneira assíncrona de copiar de um fluxo para outro, eis um padrão que usei com sucesso em um aplicativo de encaminhamento de porta para copiar dados de um fluxo de rede para outro. Falta manipulação de exceção para enfatizar o padrão.
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);
}
Fácil e seguro - crie um novo fluxo a partir da fonte original:
MemoryStream source = new MemoryStream(byteArray);
MemoryStream copy = new MemoryStream(byteArray);