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

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

12 ответов


из .NET 4.5 on, есть 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? Потому что эта длина может быть недоступна! От docs:

если класс, производный от Stream, не поддерживает поиск, вызовы Length, SetLength, Position и Seek throw a исключение 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" класса потока системы.Пространство имен 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 для работы непосредственно с массивом байтов, представляющим ваши данные. Это позволяет использовать такие методы, как массив.CopyTo, который абстрагирует все вопросы, поднятые fryguybob. Вы можете просто доверять .NET, чтобы знать оптимальный способ копирования данных.


Если вы хотите, чтобы procdure скопировал поток на другой, который Ник опубликовал, в порядке, но ему не хватает сброса позиции, он должен быть

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
}

но если он во время выполнения не использует процедуру, вы shpuld использовать поток памяти

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