Відповіді:
Від .NET 4.5 далі є Stream.CopyToAsync
метод
input.CopyToAsync(output);
Це поверне значення, 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: Цей метод дозволить вам звітувати про прогрес (x байтів, прочитаних дотепер ...)
Примітка 2: Навіщо використовувати фіксований розмір буфера, а ні input.Length
? Оскільки ця довжина може бути недоступною! З документів :
Якщо клас, похідний від Stream, не підтримує пошук, дзвонить у Length, SetLength, Position та Seek, щоб кинути NotSupportedException.
81920
байтів, ні32768
MemoryStream має .WriteTo (вихідний);
і .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" метод класу Stream у просторі імен 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);
}