Каков наилучший способ скопировать содержимое одного потока в другой? Есть ли для этого стандартный метод утилит?
Каков наилучший способ скопировать содержимое одного потока в другой? Есть ли для этого стандартный метод утилит?
Ответы:
Начиная с .NET 4.5 существует Stream.CopyToAsync
метод
input.CopyToAsync(output);
Это вернет a, Task
который можно продолжить после завершения, например, так:
await input.CopyToAsync(output)
// Code from here on will be run in a continuation.
Обратите внимание, что в зависимости от того, где CopyToAsync
сделан вызов , следующий код может продолжаться или не продолжаться в том же потоке, который его вызвал.
То, SynchronizationContext
что было перехвачено при вызове await
, определит, в каком потоке будет выполняться продолжение.
Кроме того, этот вызов (и это деталь реализации, подлежащая изменению) все еще выполняет чтение и запись последовательности (он просто не тратит потоки, блокирующие завершение ввода-вывода).
Начиная с .NET 4.0, есть Stream.CopyTo
метод
input.CopyTo(output);
Для .NET 3.5 и раньше
Ничто не запечено в рамках, чтобы помочь с этим; Вы должны скопировать содержимое вручную, например так:
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);
}
}
Примечание 1: Этот метод позволит вам сообщать о прогрессе (пока прочитано х байтов ...)
Примечание 2: Почему используется фиксированный размер буфера, а не input.Length
? Потому что эта длина может быть недоступна! Из документов :
Если класс, производный от Stream, не поддерживает поиск, вызовы Length, SetLength, Position и Seek выдают исключение NotSupportedException.
81920
байтов, а не32768
MemoryStream имеет .WriteTo (outstream);
и .NET 4.0 имеет .CopyTo для обычного объекта потока.
.NET 4.0:
instream.CopyTo(outstream);
Я использую следующие методы расширения. Они оптимизировали перегрузки для случая, когда один поток является 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);
}
Основные вопросы, которые отличают реализации «CopyStream»:
Ответы на эти вопросы приводят к совершенно разным реализациям CopyStream и зависят от того, какие потоки у вас есть и что вы пытаетесь оптимизировать. «Лучшая» реализация даже должна знать, на каком конкретном оборудовании потоки читают и записывают данные.
На самом деле существует менее жесткий способ создания потоковой копии. Обратите внимание, однако, что это означает, что вы можете хранить весь файл в памяти. Не пытайтесь использовать это, если вы работаете с файлами, которые занимают сотни мегабайт или более, без осторожности.
public static void CopyStream(Stream input, Stream output)
{
using (StreamReader reader = new StreamReader(input))
using (StreamWriter writer = new StreamWriter(output))
{
writer.Write(reader.ReadToEnd());
}
}
ПРИМЕЧАНИЕ. Могут также возникнуть некоторые проблемы, касающиеся двоичных данных и кодировки символов.
reader.ReadToEnd()
помещает все в оперативную память
.NET Framework 4 представляет новый метод «CopyTo» для класса потока пространства имен System.IO. Используя этот метод, мы можем скопировать один поток в другой поток другого класса потока.
Вот пример для этого.
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()
приветствуется.
К сожалению, нет действительно простого решения. Вы можете попробовать что-то вроде этого:
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();
Но проблема в том, что другая реализация класса Stream может вести себя по-разному, если нечего читать. Поток, считывающий файл с локального жесткого диска, вероятно, будет блокироваться до тех пор, пока операция чтения не прочитает достаточно данных с диска, чтобы заполнить буфер, и вернет меньше данных, только если достигнет конца файла. С другой стороны, потоковое чтение из сети может вернуть меньше данных, даже если осталось больше данных для получения.
Всегда проверяйте документацию определенного класса потока, который вы используете, прежде чем использовать универсальное решение.
Может быть способ сделать это более эффективно, в зависимости от того, с каким потоком вы работаете. Если вы можете преобразовать один или оба ваших потока в MemoryStream, вы можете использовать метод GetBuffer для работы непосредственно с байтовым массивом, представляющим ваши данные. Это позволяет вам использовать такие методы, как Array.CopyTo, которые абстрагируют все проблемы, поднятые fryguybob. Вы можете просто доверять .NET, чтобы знать оптимальный способ копирования данных.
если вы хотите, чтобы процедура копировала поток в другой, который отправил ник, это нормально, но в нем отсутствует сброс позиции, это должно быть
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
}
но если он во время выполнения не использует процедуру, вы должны использовать поток памяти
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
Поскольку ни один из ответов не охватывал асинхронный способ копирования из одного потока в другой, здесь приведен шаблон, который я успешно использовал в приложении переадресации портов для копирования данных из одного сетевого потока в другой. В нем отсутствует обработка исключений, чтобы подчеркнуть шаблон.
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);
}