Bir akışın içeriğini diğerine nasıl kopyalarım?


521

Bir akışın içeriğini diğerine kopyalamanın en iyi yolu nedir? Bunun için standart bir yardımcı yöntem var mı?


Belki daha da önemlisi, bu noktada içeriği "akıcı şekilde" nasıl kopyalarsınız, yani kaynak akışı yalnızca bir şey hedef akışı tüketirken kopyalar.
drzaus

Yanıtlar:


694

.NET 4.5'ten itibaren, Stream.CopyToAsyncyöntem var

input.CopyToAsync(output);

Bu, Tasktamamlandığında devam edebilecek bir şekilde geri dönecektir, şöyle:

await input.CopyToAsync(output)

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

Çağrının yapıldığı yere bağlı olarak CopyToAsync, izleyen kodun onu çağıran aynı iş parçacığında devam edip etmeyebileceğini unutmayın.

SynchronizationContextÇağrılırken yakalandığı awaitdevamı üzerinde yürütülecek iplik neyi belirleyecektir.

Ek olarak, bu çağrı (ve bu değişime tabi bir uygulama detayıdır) hala okuma ve yazma dizileri (sadece G / Ç tamamlanmasını engelleyen bir iş parçacığını boşa harcamaz).

.NET 4.0'dan itibaren, Stream.CopyToyöntem var

input.CopyTo(output);

.NET 3.5 ve öncesi için

Buna yardımcı olmak için çerçevede pişmiş hiçbir şey yoktur; içeriği manuel olarak kopyalamanız gerekir, şöyle:

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

Not 1: Bu yöntem ilerlemeyi raporlamanıza izin verir (şimdiye kadar okunan x bayt ...)
Not 2: Neden sabit bir tampon boyutu kullanalım input.Length? Çünkü bu Uzunluk mevcut olmayabilir! Gönderen docs :

Stream'den türetilen bir sınıf aramayı desteklemiyorsa, Length, SetLength, Position ve Seek çağrıları bir NotSupportedException özel durumu oluşturur.


58
Bunun en hızlı yol olmadığını unutmayın. Sağlanan kod snippet'inde, yeni bir blok okunmadan önce Yazma işleminin tamamlanmasını beklemeniz gerekir. Okuma ve Yazma'yı eşzamansız olarak yaparken bu bekleme kaybolacaktır. Bazı durumlarda bu kopyayı iki kat daha hızlı yapar. Ancak, kod çok daha karmaşık hale gelecektir, bu nedenle hız bir sorun değilse, basit tutun ve bu basit döngüyü kullanın. StackOverflow ile ilgili bu soruda, zaman uyumsuz Okuma / Yazma'yı gösteren bazı kodlar bulunmaktadır: stackoverflow.com/questions/1540658/… Saygılarımızla, Sebastiaan
Sebastiaan M

16
FWIW, testlerimde 4096'nın aslında 32K'dan daha hızlı olduğunu gördüm. CLR'nin belirli bir boyutta parçaları nasıl tahsis ettiği ile ilgili bir şey. Bu nedenle, Stream.CopyTo .NET uygulaması 4096 kullanır.
Jeff

1
CopyToAsync'in nasıl uygulandığını bilmek veya yaptığım gibi değişiklikler yapmak istiyorsanız (kopyalanacak maksimum bayt sayısını belirleyebilmem gerekiyordu), ".NET Framework ile Paralel Programlama Örnekleri" code.msdn
Michael

1
FIY, optimal arabellek boyutu iz 81920bayt değil,32768
Alex Zhukovskiy

2
@Jeff latest referecnceSource , aslında 81920 bayt arabelleği kullandığını gösterir.
Alex Zhukovskiy

66

MemoryStream öğesinde .WriteTo (akış dışı);

ve .NET 4.0 normal akış nesnesinde .CopyTo öğesine sahiptir.

.NET 4.0:

instream.CopyTo(outstream);

Bu yöntemleri kullanarak web üzerinde çok fazla örnek görmüyorum. Bu oldukça yeni oldukları için mi yoksa bazı sınırlamalar var mı?
GeneS

3
Bunun nedeni .NET 4.0'da yeni olmalarıdır. Stream.CopyTo () temel olarak, onaylanmış yanıtın yaptığı döngü için tam olarak aynıdır ve bazı ek sağlık kontrolleri yapar. Varsayılan arabellek boyutu 4096'dır, ancak daha büyük olanı belirtmek için aşırı yükleme de vardır.
Michael Edenfield

9
Akışın kopyadan sonra geri sarılması gerekir: instream.Position = 0;
Draykos

6
Giriş akışını geri sarmaya ek olarak, çıkış akışını geri sarmaya ihtiyaç duydum: outstream.Position = 0;
JonH

32

Aşağıdaki uzantı yöntemlerini kullanıyorum. Bir akış bir MemoryStream olduğunda aşırı yüklenmeleri optimize ettiler.

    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

"CopyStream" uygulamalarını farklılaştıran temel sorular şunlardır:

  • okuma tamponunun boyutu
  • yazıların boyutu
  • Birden fazla iş parçacığı kullanabilir miyiz (okurken yazma).

Bu soruların cevapları CopyStream'in çok farklı uygulamalarıyla sonuçlanır ve ne tür akışlara sahip olduğunuza ve neyi optimize etmeye çalıştığınıza bağlıdır. "En iyi" uygulamanın, akışların hangi belirli donanımı okuduğunu ve yazdığını bile bilmesi gerekir.


1
... veya en iyi uygulama, arabellek boyutunu, yazma boyutunu ve iş parçacıklarına izin verilip verilmeyeceğini belirtmenize izin veren aşırı yüklenmelere sahip olabilir mi?
MarkJ

1

Aslında, bir kopyasını yapmanın daha az ağır bir yolu var. Ancak bunun tüm dosyayı bellekte saklayabileceğinizi unutmayın. Dikkatli olarak yüzlerce megabayt veya daha fazlasına giren dosyalarla çalışıyorsanız bunu denemeyin ve kullanmayın.

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

NOT: İkili veri ve karakter kodlamaları ile ilgili bazı sorunlar da olabilir.


6
StreamWriter için varsayılan kurucu, BOM'siz bir UTF8 akışı oluşturur ( msdn.microsoft.com/en-us/library/fysy0a4b.aspx ), bu nedenle kodlama problemleri tehlikesi yoktur. İkili veriler neredeyse kesinlikle bu şekilde kopyalanmamalıdır.
kͩeͣmͮpͥ ͩ

14
kolayca "tüm dosyayı bellekte" yüklemenin "daha az ağır" olduğu düşünülebilir.
Seph

Ben bu nedenle outmemory istisna olsun
ColacX

Bu akış için akış değil . reader.ReadToEnd()her şeyi RAM'e koyar
Bizhan

1

.NET Framework 4, System.IO ad alanının Stream Class'ın yeni "CopyTo" yöntemini sunar. Bu yöntemi kullanarak bir akışı farklı akış sınıfının başka bir akışına kopyalayabiliriz.

İşte bunun için örnek.

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

Hatırlatma: CopyToAsync()kullanımı teşvik edilir.
Jari Turkia

0

Ne yazık ki, gerçekten basit bir çözüm yok. Böyle bir şey deneyebilirsiniz:

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

Ancak, okunacak hiçbir şey yoksa, Stream sınıfının farklı uygulamasının farklı davranabilmesiyle ilgili sorun. Yerel bir sabit sürücüden dosya okuyan bir akış, okuma işlemi diskten arabelleği dolduracak ve yalnızca dosya sonuna ulaştığında daha az veri döndürene kadar yeterli veri okuyana kadar engellenir. Öte yandan, ağdan okunan bir akım, alınacak daha fazla veri olmasına rağmen daha az veri döndürebilir.

Genel bir çözüm kullanmadan önce her zaman kullandığınız belirli sınıfın belgelerine bakın.


5
Jenerik çözüm burada işe yarar - Nick'in cevabı iyi bir çözümdür. Arabellek boyutu keyfi bir seçimdir, ancak 32K makul görünüyor. Bence Nick'in çözümü akarsuları kapatmak için doğru değil - bunu sahibine bırakın.
Jon Skeet

0

Hangi tür akışla çalıştığınıza bağlı olarak bunu daha verimli bir şekilde yapmanın bir yolu olabilir. Akışlarınızdan birini veya her ikisini MemoryStream'e dönüştürebiliyorsanız, doğrudan verilerinizi temsil eden bir bayt dizisiyle çalışmak için GetBuffer yöntemini kullanabilirsiniz. Bu, fryguybob tarafından ortaya atılan tüm sorunları soyutlayan Array.CopyTo gibi yöntemleri kullanmanıza izin verir. Verileri kopyalamanın en iyi yolunu bilmek için .NET'e güvenebilirsiniz.


0

bir prodüksiyonun bir yayını, nickin yayınladığı bir akışa kopyalamasını istiyorsanız, ancak konum sıfırlama eksikse,

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
}

ancak çalışma zamanında bir prosedür kullanmıyorsa, bellek akışını kullanmalısınız

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
Giriş akışının konumunu değiştirmemelisiniz, çünkü tüm akışlar rasgele erişime izin vermez. Örneğin bir ağ akışında konumu değiştiremezsiniz, sadece okuma ve / veya yazma yapabilirsiniz.
R. Martinho Fernandes

0

Yanıtların hiçbiri bir akıştan diğerine eşzamanlı olmayan bir kopyalama yöntemi içermediğinden, burada bir bağlantı yönlendirme uygulamasında bir ağ akışından diğerine veri kopyalamak için başarıyla kullandığım bir model var. Deseni vurgulamak için istisna yönetimi yoktur.

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
Şüphesiz, ikinci okuma ilk yazmadan önce tamamlanırsa, yazılmadan önce bufferForWrite'ın içeriğini ilk okumadan yazacaksınız.
Peter Jeffery

0

.NET 3.5 ve öncesi için deneyin:

MemoryStream1.WriteTo(MemoryStream2);

Bu sadece MemoryStreams ile uğraşıyorsanız işe yarar.
Nyerguds

0

Kolay ve güvenli - orijinal kaynaktan yeni akış yapın:

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.