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

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

12 ответов

Решение

Начиная с версии.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);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

MemoryStream1.WriteTo(MemoryStream2);

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

Stream stream = new MemoryStream();

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

MemoryStream newMs = (MemoryStream)stream;

byte[] getByte = newMs.ToArray();

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

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