Як скопіювати вміст одного потоку в інший?


521

Який найкращий спосіб скопіювати вміст одного потоку в інший? Чи існує для цього стандартний корисний метод?


Можливо, важливіше в цей момент, як ви копіюєте вміст "streamably", це означає, що він лише копіює вихідний потік, як щось споживає цільовий потік ...?
drzaus

Відповіді:


694

Від .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.


58
Зауважте, що це не найшвидший спосіб зробити це. У наданому фрагменті коду вам потрібно дочекатися завершення запису, перш ніж прочитати новий блок. Під час читання та запису асинхронно це очікування зникне. У деяких випадках це зробить копію вдвічі швидшою. Однак це зробить код набагато складнішим, тому, якщо швидкість не є проблемою, будьте прості та використовуйте цей простий цикл. У цьому запитанні на StackOverflow є якийсь код, який ілюструє асинхронізування Read / Write: stackoverflow.com/questions/1540658/… З повагою, Себастіян
Себастіян M

16
FWIW, на своєму тестуванні я виявив, що 4096 насправді швидше 32K. Щось із тим, як CLR виділяє шматки певного розміру. Через це .NET реалізація Stream.CopyTo, мабуть, використовує 4096.
Jeff

1
Якщо ви хочете знати, як реалізується CopyToAsync, або вносити зміни, як я (мені потрібно було вказати максимальну кількість байтів для копіювання), він доступний як CopyStreamToStreamAsync у "Зразки для паралельного програмування з .NET Framework" code.msdn .microsoft.com / ParExtSamples
Майкл

1
FIY, оптимальний розмір буфера із 81920байтів, ні32768
Олексій Жуковський

2
Останній референтний ресурс @Jeff показує, що він фактично використовує буфер 81920 байт.
Олексій Жуковський

66

MemoryStream має .WriteTo (вихідний);

і .NET 4.0 має .CopyTo на звичайному об'єкті потоку.

.NET 4.0:

instream.CopyTo(outstream);

Я не бачу багатьох зразків в Інтернеті, використовуючи ці методи. Це тому, що вони є досить новими чи є деякі обмеження?
GeneS

3
Це тому, що вони є новими в .NET 4.0. Stream.CopyTo () в основному робить так само для циклу, що і затверджена відповідь, з деякими додатковими перевірками надійності. Типовий розмір буфера - 4096, але також є перевантаження, щоб вказати більший.
Майкл Еденфілд

9
Потік потрібно перемотувати після копіювання: instream.Position = 0;
Драйкос

6
Крім перемотування потоку введення, я також виявив необхідність перемотати вихідний потік: outstream.Position = 0;
JonH

32

Я використовую такі методи розширення. Вони оптимізували перевантаження, коли один потік є 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);
    }

1

Основними питаннями, які відрізняють реалізацію "CopyStream", є:

  • розмір буфера для читання
  • розмір записів
  • Чи можемо ми використовувати більше однієї нитки (пишемо, поки читаємо).

Відповіді на ці запитання призводять до абсолютно різних реалізацій CopyStream і залежать від того, які потоки у вас є і що ви намагаєтеся оптимізувати. "Найкращому" впровадженню навіть потрібно було б знати, до якого конкретного обладнання читали та записували потоки.


1
... або найкраща реалізація може мати перевантаження, щоб ви могли вказати розмір буфера, розмір запису та чи дозволені потоки?
MarkJ

1

Насправді існує менш важкий спосіб зробити поточну копію. Зауважте, що це означає, що ви можете зберігати весь файл у пам'яті. Не намагайтеся використовувати це, якщо ви працюєте з файлами, що входять у сотні мегабайт або більше, без обережності.

public static void CopyStream(Stream input, Stream output)
{
  using (StreamReader reader = new StreamReader(input))
  using (StreamWriter writer = new StreamWriter(output))
  {
    writer.Write(reader.ReadToEnd());
  }
}

ПРИМІТКА. Можуть також виникнути проблеми, пов’язані з бінарними даними та кодуваннями символів.


6
Конструктор за замовчуванням для StreamWriter створює потік UTF8 без BOM ( msdn.microsoft.com/en-us/library/fysy0a4b.aspx ), тому немає небезпеки виникнення проблем з кодуванням. Бінарні дані майже напевно не слід копіювати таким чином.
kͩeͣmͮpͥ ͩ

14
можна легко стверджувати, що завантаження "всього файлу в пам'яті" навряд чи вважається "менш важким".
Сеф

Я отримую ексклюзивне виняток через це
ColacX

Це не потік для потоку. reader.ReadToEnd()ставить все в оперативну пам’ять
Bizhan

1

.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().
Ярі

0

На жаль, не існує справді простого рішення. Ви можете спробувати щось подібне:

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 може поводитися по-різному, якщо нічого не можна прочитати. Потік, що читає файл з локального жорсткого диска, ймовірно, блокується, поки операція зчитування не прочитає достатньо даних з диска для заповнення буфера і поверне менше даних, якщо він досягне кінця файлу. З іншого боку, потік зчитування з мережі може повернути менше даних, навіть якщо залишилось більше даних для отримання.

Завжди перевіряйте документацію конкретного класу потоку, який ви використовуєте, перш ніж використовувати загальне рішення.


5
Тут працюватиме загальне рішення - відповідь Ніка - тонка. Розмір буфера - це довільний вибір курсу, але 32K звучить розумно. Я думаю, що рішення Ніка правильне не закривати потоки, хоча - залиште це власнику.
Джон Скіт

0

Можливо, є спосіб зробити це більш ефективно, залежно від того, з яким потоком ви працюєте. Якщо ви можете конвертувати один або обидва ваших потоку в MemoryStream, ви можете використовувати метод GetBuffer для роботи безпосередньо з байтовим масивом, що представляє ваші дані. Це дозволяє використовувати такі методи, як Array.CopyTo, які абстрагують усі проблеми, порушені fryguybob. Ви можете просто довіритися .NET, щоб знати оптимальний спосіб копіювання даних.


0

якщо ви хочете, щоб замовлення скопіювало потік на інший, який розмістив Нік, добре, але у нього відсутнє скидання позиції, воно повинно бути

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

3
Не слід змінювати положення вхідного потоку, оскільки не всі потоки дозволяють отримати випадковий доступ. Наприклад, у мережевому потоці ви не можете змінювати положення, лише читати та / або записувати.
Р. Мартіньо Фернандес

0

Оскільки жодна з відповідей не охоплювала асинхронний спосіб копіювання з одного потоку в інший, ось такий шаблон, який я успішно використовував у програмі переадресації портів для копіювання даних з одного мережевого потоку в інший. Не вистачає поводження з винятками, щоб підкреслити модель.

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

4
Безумовно, якщо друге читання завершиться до першого запису, тоді ви напишете вміст bufferForWrite з першого читання, перш ніж воно буде виписано.
Пітер Джеффрі

0

Для .NET 3.5 та перед спробу:

MemoryStream1.WriteTo(MemoryStream2);

Це працює лише в тому випадку, якщо ви маєте справу з MemoryStreams.
Nyerguds

0

Легко та безпечно - створіть новий потік з оригінального джерела:

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.