Как скопировать содержимое одного потока в другой?

Как лучше всего скопировать содержимое одного потока в другой? Есть ли для этого стандартный служебный метод?

Может быть, еще более важно на этом этапе, как вы копируете содержимое "в потоковом режиме", что означает, что он копирует только исходный поток, поскольку что-то потребляет поток назначения ...?

drzaus 14.10.2015 01:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
532
1
316 633
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Ответ принят как подходящий

Начиная с .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? Потому что эта длина может быть недоступна! Из документы:

If a class derived from Stream does not support seeking, calls to Length, SetLength, Position, and Seek throw a NotSupportedException.

Учтите, что это не самый быстрый способ сделать это. В предоставленном фрагменте кода вам нужно дождаться завершения записи, прежде чем будет прочитан новый блок. При выполнении асинхронного чтения и записи это ожидание исчезнет. В некоторых случаях это сделает копию вдвое быстрее. Однако это значительно усложнит код, поэтому, если скорость не является проблемой, оставьте его простым и используйте этот простой цикл. В этом вопросе на StackOverflow есть код, который иллюстрирует асинхронное чтение / запись: stackoverflow.com/questions/1540658/… С уважением, Себастьян

Sebastiaan M 06.11.2009 10:10

32 768 - рекомендуемый размер байта? Раньше я видел меньшие суммы, например 4096.

Josh 07.01.2011 19:02

@Josh 32,768 - это копия размером 32 КБ. 4096 использует блоки размером 4 КБ. Это будет немного медленнее, но потребует меньше памяти.

Nick 07.01.2011 19:11

FWIW: input.CopyTo (output) делает то же самое, что и этот метод CopyStream, за исключением того, что он использует блоки размером 4K и выполняет некоторую защитную проверку ввода вокруг состояния потока. О, и, как предположил @Jon, они использовали идиому while ((bytes = this.Read (...))! = 0).

hemp 08.01.2011 03:11

Было бы целесообразно проверить наличие input.CanRead и output.CanWrite?

Iain Sproat 20.05.2011 21:47

FWIW, в своем тестировании я обнаружил, что 4096 на самом деле быстрее 32 КБ. Что-то связано с тем, как CLR выделяет фрагменты определенного размера. Из-за этого реализация Stream.CopyTo .NET, по-видимому, использует 4096.

Jeff 18.02.2012 04:03

Если у вас проблемы, как у меня. добавьте это перед строкой copyTo (). sourceStream.Seek (0, SeekOrigin.Begin);

ThiagoPXP 31.07.2012 04:46

Если вы хотите узнать, как реализован CopyToAsync, или внести изменения, как это сделал я (мне нужно было указать максимальное количество байтов для копирования), тогда он доступен как CopyStreamToStreamAsync в разделе «Примеры параллельного программирования с .NET Framework» code.msdn.microsoft.com/ParExtSamples

Michael 02.06.2013 06:00

@ThiagoPXP У меня также были проблемы, когда input был MemoryStream - не заметил вашего комментария, но смог исправить с помощью .WriteTo, а не .CopyTo. Я думаю, что с вашим предложением вы также должны проверить .CanSeek, прежде чем пытаться искать.

drzaus 17.01.2014 19:34

FIY, оптимальный размер буфера - 81920 байта, а не 32768

Alex Zhukovskiy 21.06.2016 11:51

@Jeff последняя ссылка показывает, что он фактически использует буфер 81920 байт.

Alex Zhukovskiy 21.06.2016 11:52

Разве await input.CopyToAsync(output) не должен быть await input.CopyToAsync(output).ContinueWith(t => /*do something with output*/);? Я думаю, что это имеет больше смысла, потому что, вероятно, разработчик хочет работать с выходным потоком.

N-ate 10.04.2019 19:07

К сожалению, действительно простого решения не существует. Вы можете попробовать что-то вроде этого:

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

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

Общее решение здесь будет работать - ответ Ника хороший. Размер буфера, конечно, произвольный, но 32 КБ звучит разумно. Я думаю, что решение Ника - правильное решение - не закрывать потоки - оставьте это владельцу.

Jon Skeet 23.10.2008 19:31

Основные вопросы, которые отличают реализации CopyStream:

  • размер буфера чтения
  • размер записей
  • Можем ли мы использовать более одного потока (запись, пока мы читаем).

Ответы на эти вопросы приводят к совершенно разным реализациям CopyStream и зависят от того, какие потоки у вас есть и что вы пытаетесь оптимизировать. «Лучшая» реализация даже должна знать, на каком конкретном оборудовании потоки читают и записывают.

... или лучшая реализация могла бы иметь перегрузки, чтобы вы могли указать размер буфера, размер записи и разрешены ли потоки?

MarkJ 23.03.2010 19:40

Возможно, есть способ сделать это более эффективно, в зависимости от того, с каким потоком вы работаете. Если вы можете преобразовать один или оба своих потока в 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

Вы не должны изменять положение входного потока, потому что не все потоки допускают произвольный доступ. В сетевом потоке, например, вы не можете изменить позицию, только чтение и / или запись.

R. Martinho Fernandes 20.10.2010 04:50

На самом деле существует менее жесткий способ копирования потока. Однако обратите внимание, что это означает, что вы можете хранить весь файл в памяти. Не пытайтесь использовать это, если вы работаете с файлами, размер которых превышает сотни мегабайт, без осторожности.

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

ПРИМЕЧАНИЕ. Также могут возникнуть проблемы с двоичными данными и кодировками символов.

Конструктор по умолчанию для StreamWriter создает поток UTF8 без спецификации (msdn.microsoft.com/en-us/library/fysy0a4b.aspx), поэтому нет опасности возникновения проблем с кодированием. Двоичные данные почти наверняка не следует копировать таким образом.

kͩeͣmͮpͥ ͩ 09.11.2009 19:27

Можно легко возразить, что загрузка «всего файла в память» вряд ли считается «менее жесткой».

Seph 03.01.2012 16:23

я получаю исключение outmemory из-за этого

ColacX 16.12.2016 17:59

Это поток нет для потоковой передачи. reader.ReadToEnd() помещает все в оперативную память

Bizhan 11.05.2020 17:01

Я переименовал метод с CopyStream () в CopySmallTextStream (). Возможно, это поможет сделать недостатки этого решения более очевидными в последующих базах кода.

hannasm 05.08.2020 04:55

Я использую следующие методы расширения. Они оптимизировали перегрузки для случаев, когда одним потоком является 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);
    }

MemoryStream имеет .WriteTo(outstream);

а .NET 4.0 имеет .CopyTo в обычном потоковом объекте.

.NET 4.0:

instream.CopyTo(outstream);

Я не вижу много примеров в Интернете, использующих эти методы. Это потому, что они довольно новые, или есть какие-то ограничения?

GeneS 24.11.2011 21:21

Это потому, что они впервые появились в .NET 4.0. Stream.CopyTo () в основном делает то же самое, что и утвержденный ответ, с некоторыми дополнительными проверками работоспособности. Размер буфера по умолчанию - 4096, но есть также перегрузка, чтобы указать больший размер.

Michael Edenfield 02.12.2011 02:28

После копирования поток нужно перемотать: instream.Position = 0;

Draykos 24.01.2013 19:37

В дополнение к перемотке входного потока я также обнаружил необходимость перемотать выходной поток: outstream.Position = 0;

JonH 27.03.2015 18:40

Поскольку ни один из ответов не охватывал асинхронный способ копирования из одного потока в другой, вот шаблон, который я успешно использовал в приложении переадресации портов для копирования данных из одного сетевого потока в другой. В нем отсутствует обработка исключений, чтобы подчеркнуть шаблон.

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

Конечно, если второе чтение завершится до первой записи, тогда вы запишете содержимое bufferForWrite из первого чтения, прежде чем оно будет записано.

Peter Jeffery 18.11.2011 13:50

.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/>");

Для .NET 3.5 и до этого попробуйте:

MemoryStream1.WriteTo(MemoryStream2);

Это работает, только если вы имеете дело с MemoryStreams.

Nyerguds 12.02.2019 11:19

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

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);

Следующий код для решения проблемы скопируйте Stream в MemoryStream с помощью CopyTo

Stream stream = new MemoryStream();

// любая функция требует ввода потока. В моем случае, чтобы сохранить файл PDF как поток document.Save (поток);

MemoryStream newMs = (MemoryStream)stream;

byte[] getByte = newMs.ToArray();

// Примечание - разместите поток в блоке finally, а не в блоке using, так как он выдаст ошибку «Доступ запрещен, поскольку поток закрыт»

Добро пожаловать в Stack Overflow. Добавляя ответ на вопрос одиннадцатилетней давности с двенадцатью существующими ответами, включая принятый ответ, очень важно указать, к какому новому аспекту вопроса относится ваш ответ. Используйте форматирование кода для кода. Ответы лучше, если они включают объяснение того, как и почему они работают.

Jason Aller 06.08.2020 07:19

Другие вопросы по теме