Làm cách nào để sao chép nội dung của luồng này sang luồng khác?


521

Cách tốt nhất để sao chép nội dung của luồng này sang luồng khác là gì? Có một phương pháp tiện ích tiêu chuẩn cho việc này?


Có lẽ quan trọng hơn vào thời điểm này, làm thế nào để bạn sao chép nội dung "một cách hợp lý", nghĩa là nó chỉ sao chép luồng nguồn khi một thứ gì đó tiêu thụ luồng đích ...?
drzaus

Câu trả lời:


694

Từ .NET 4.5 trở đi, có Stream.CopyToAsyncphương thức

input.CopyToAsync(output);

Điều này sẽ trả về một Taskcó thể được tiếp tục khi hoàn thành, như vậy:

await input.CopyToAsync(output)

// Code from here on will be run in a continuation.

Lưu ý rằng tùy thuộc vào nơi cuộc gọi CopyToAsyncđược thực hiện, mã theo sau có thể hoặc không thể tiếp tục trên cùng một chuỗi đã gọi nó.

Cái SynchronizationContextđược chụp khi gọi awaitsẽ xác định luồng nào sẽ tiếp tục được thực hiện.

Ngoài ra, cuộc gọi này (và đây là một chi tiết triển khai có thể thay đổi) vẫn tiếp tục đọc và ghi (nó chỉ không lãng phí một chuỗi chặn khi hoàn thành I / O).

Từ .NET 4.0 trở đi, có Stream.CopyTophương thức

input.CopyTo(output);

Đối với .NET 3.5 trở về trước

Không có bất cứ điều gì được đưa vào khuôn khổ để hỗ trợ việc này; bạn phải sao chép nội dung theo cách thủ công, như vậy:

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

Lưu ý 1: Phương pháp này sẽ cho phép bạn báo cáo về tiến trình (x byte đọc cho đến nay ...)
Lưu ý 2: Tại sao nên sử dụng kích thước bộ đệm cố định mà không phải input.Length? Bởi vì chiều dài đó có thể không có sẵn! Từ các tài liệu :

Nếu một lớp có nguồn gốc từ Luồng không hỗ trợ tìm kiếm, hãy gọi đến Độ dài, Đặt độ dài, Vị trí và Tìm kiếm ném một ngoại lệ NotSupportedException.


58
Lưu ý rằng đây không phải là cách nhanh nhất để làm điều đó. Trong đoạn mã được cung cấp, bạn phải đợi Viết hoàn thành trước khi đọc một khối mới. Khi thực hiện Đọc và Viết không đồng bộ, sự chờ đợi này sẽ biến mất. Trong một số tình huống, điều này sẽ làm cho bản sao nhanh gấp đôi. Tuy nhiên, nó sẽ làm cho mã phức tạp hơn rất nhiều vì vậy nếu tốc độ không phải là vấn đề, hãy giữ nó đơn giản và sử dụng vòng lặp đơn giản này. Câu hỏi này trên StackOverflow có một số mã minh họa async Đọc / Viết: stackoverflow.com/questions/1540658/ Reg Regards, Sebastiaan
Sebastiaan M

16
FWIW, trong thử nghiệm của tôi, tôi đã thấy rằng 4096 thực sự nhanh hơn 32K. Một cái gì đó để làm với cách CLR phân bổ các khối trên một kích thước nhất định. Do đó, việc triển khai .NET của Stream.CopyTo rõ ràng sử dụng 4096.
Jeff

1
Nếu bạn muốn biết làm thế nào CopyToAsync được thực hiện hoặc thực hiện những thay đổi như tôi đã làm (tôi cần để có thể xác định số lượng tối đa byte để sao chép) sau đó nó có sẵn như CopyStreamToStreamAsync trong "Mẫu cho Lập trình song song với .NET Framework" code.msdn .microsoft.com / ParExtSamples
Michael

1
FIY, kích thước bộ đệm tối ưu iz 81920byte, không phải32768
Alex Zhukovskiy

2
Tài liệu tham khảo mới nhất của @Jeff cho thấy rằng nó thực sự sử dụng bộ đệm 81920 byte.
Alex Zhukovskiy

66

MemoryStream có .WriteTo (ngoài luồng);

và .NET 4.0 có .CopyTo trên đối tượng luồng bình thường.

.NET 4.0:

instream.CopyTo(outstream);

Tôi không thấy nhiều mẫu trên web sử dụng các phương pháp này. Đây có phải là vì chúng khá mới hoặc có một số hạn chế?
GeneS

3
Đó là vì chúng mới trong .NET 4.0. Stream.CopyTo () về cơ bản thực hiện chính xác như vậy đối với vòng lặp mà câu trả lời được phê duyệt thực hiện, với một số kiểm tra độ tỉnh bổ sung. Kích thước bộ đệm mặc định là 4096, nhưng cũng có một quá tải để chỉ định một bộ đệm lớn hơn.
Michael Edenfield

9
Luồng cần được tua lại sau khi sao chép: instream.Pocation = 0;
Draykos

6
Ngoài việc tua lại luồng đầu vào, tôi cũng thấy cần phải tua lại luồng đầu ra: outstream.Pocation = 0;
JonH

32

Tôi sử dụng các phương pháp mở rộng sau đây. Họ đã tối ưu hóa quá tải khi một luồng là 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

Các câu hỏi cơ bản phân biệt việc triển khai "CopyStream" là:

  • kích thước của bộ đệm đọc
  • kích thước của ghi
  • Chúng ta có thể sử dụng nhiều hơn một chủ đề (viết trong khi chúng ta đang đọc).

Các câu trả lời cho những câu hỏi này dẫn đến việc triển khai CopyStream rất khác nhau và phụ thuộc vào loại luồng bạn có và những gì bạn đang cố gắng tối ưu hóa. Việc triển khai "tốt nhất" thậm chí sẽ cần biết phần cứng cụ thể mà các luồng đang đọc và ghi vào.


1
... hoặc việc triển khai tốt nhất có thể có quá tải để cho phép bạn chỉ định kích thước bộ đệm, kích thước ghi và liệu các luồng có được phép không?
MarkJ

1

Thực sự, có một cách ít nặng nề hơn để thực hiện một bản sao luồng. Tuy nhiên, hãy lưu ý rằng điều này ngụ ý rằng bạn có thể lưu trữ toàn bộ tệp trong bộ nhớ. Đừng thử và sử dụng điều này nếu bạn đang làm việc với các tệp đi vào hàng trăm megabyte trở lên, không cần thận trọng.

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

LƯU Ý: Cũng có thể có một số vấn đề liên quan đến dữ liệu nhị phân và mã hóa ký tự.


6
Hàm tạo mặc định cho StreamWriter tạo luồng UTF8 mà không có BOM ( msdn.microsoft.com/en-us/l Library / fysy0a4b.aspx ) vì vậy không có nguy cơ xảy ra sự cố mã hóa. Dữ liệu nhị phân gần như chắc chắn không nên được sao chép theo cách này.
kͩeͣmͮpͥ

14
người ta có thể dễ dàng lập luận rằng việc tải "toàn bộ tệp trong bộ nhớ" hầu như không được coi là "ít nặng tay".
Seph

tôi nhận được ngoại lệ vì điều này
ColacX

Đây không phải là luồng để truyền phát. reader.ReadToEnd()đặt mọi thứ vào RAM
Bizhan

1

.NET Framework 4 giới thiệu phương thức "CopyTo" mới của lớp Stream của không gian tên System.IO. Sử dụng phương pháp này, chúng ta có thể sao chép một luồng sang luồng khác của lớp luồng khác.

Dưới đây là ví dụ cho điều này.

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

Nhắc nhở: sử dụng CopyToAsync()được khuyến khích.
Jari Turkia

0

Thật không may, không có giải pháp thực sự đơn giản. Bạn có thể thử một cái gì đó như thế:

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

Nhưng vấn đề với việc triển khai khác nhau của lớp Stream có thể hành xử khác đi nếu không có gì để đọc. Một luồng đọc tệp từ ổ cứng cục bộ có thể sẽ chặn cho đến khi hoạt động đọc đã đọc đủ dữ liệu từ đĩa để điền vào bộ đệm và chỉ trả lại ít dữ liệu hơn nếu đến cuối tệp. Mặt khác, việc đọc luồng từ mạng có thể trả về ít dữ liệu hơn mặc dù có nhiều dữ liệu còn lại để nhận.

Luôn kiểm tra tài liệu của lớp luồng cụ thể mà bạn đang sử dụng trước khi sử dụng giải pháp chung.


5
Giải pháp chung sẽ hoạt động ở đây - Câu trả lời của Nick là một câu hỏi hay. Tất nhiên kích thước bộ đệm là một sự lựa chọn tùy ý, nhưng 32K nghe có vẻ hợp lý. Tôi nghĩ rằng giải pháp của Nick là đúng khi không đóng luồng - hãy để lại cho chủ sở hữu.
Jon Skeet

0

Có thể có một cách để làm điều này hiệu quả hơn, tùy thuộc vào loại luồng bạn đang làm việc. Nếu bạn có thể chuyển đổi một hoặc cả hai luồng của mình thành MemoryStream, bạn có thể sử dụng phương thức GetBuffer để làm việc trực tiếp với một mảng byte biểu thị dữ liệu của bạn. Điều này cho phép bạn sử dụng các phương thức như Array.CopyTo, giúp loại bỏ tất cả các vấn đề do fryguybob nêu ra. Bạn chỉ có thể tin tưởng .NET để biết cách tối ưu để sao chép dữ liệu.


0

nếu bạn muốn một procdure sao chép luồng này sang luồng khác mà nick đã đăng thì không sao nhưng nó lại bị mất vị trí thiết lập lại, nó nên

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
}

nhưng nếu trong thời gian chạy không sử dụng thủ tục, bạn nên sử dụng luồng bộ nhớ

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
Bạn không nên thay đổi vị trí của luồng đầu vào, vì không phải tất cả các luồng đều cho phép truy cập ngẫu nhiên. Ví dụ, trong một luồng mạng, bạn không thể thay đổi vị trí, chỉ đọc và / hoặc ghi.
R. Martinho Fernandes

0

Vì không có câu trả lời nào đề cập đến cách sao chép không đồng bộ từ luồng này sang luồng khác, nên đây là mẫu mà tôi đã sử dụng thành công trong ứng dụng chuyển tiếp cổng để sao chép dữ liệu từ luồng mạng này sang luồng khác. Nó thiếu xử lý ngoại lệ để nhấn mạnh mô hình.

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
Chắc chắn nếu lần đọc thứ hai hoàn thành trước lần ghi đầu tiên thì bạn sẽ ghi lại nội dung của bộ đệmForWrite từ lần đọc đầu tiên, trước khi nó được viết ra.
Peter Jeffery

0

Đối với .NET 3.5 và trước khi thử:

MemoryStream1.WriteTo(MemoryStream2);

Điều đó chỉ hoạt động nếu bạn đang xử lý MemoryStreams.
Nyerguds

0

Dễ dàng và an toàn - tạo luồng mới từ nguồn ban đầu:

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.