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

StackOverflow https://stackoverflow.com/questions/230128

  •  04-07-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

Начиная с .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. Этот метод позволит вам сообщать о прогрессе (х байт прочитано до сих пор ...)
Примечание 2. Почему используется фиксированный размер буфера, а не input.Length ? Потому что эта длина может быть недоступна! Из документации :

  

Если класс, производный от Stream, не поддерживает поиск, вызовы Length, SetLength, Position и Seek выдают исключение NotSupportedException.

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

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

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

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

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

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

Для .NET 3.5 и до того, как попробовать:

MemoryStream1.WriteTo(MemoryStream2);

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

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top