Bir akışı C # 'daki bir dosyaya nasıl kaydedebilirim?


713

StreamReaderBir akışla başlattığım bir nesne var, şimdi bu akışı diske kaydetmek istiyorum (akış bir .gifveya .jpgveya olabilir .pdf).

Mevcut Kod:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. Bunu diske kaydetmem gerekiyor (dosya ismim var).
  2. Gelecekte bunu SQL Server'da saklamak isteyebilirim.

Ben de SQL Server saklamak gerekirse, kodlama türü var, doğru mu?


1
MyOtherObject nedir?
anhtv13

2
Bu soru için hala kabul edilmiş bir cevap yok mu?
Brett Rigby

@BrettRigby Jon Skeet Cevabı var, hemen hemen otomatik olarak kabul edildi: D
Ricardo Dias Morais

Yanıtlar:


913

Jon Skeet'in cevabında Tilendor tarafından vurgulandığı gibi, akışların CopyTo.NET 4'ten beri bir yöntemi var .

var fileStream = File.Create("C:\\Path\\To\\File");
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
fileStream.Close();

Veya usingsözdizimi ile:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}

67
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)Henüz başlangıçta değilseniz veya tüm akışı kopyalamayacaksanız arama yapmanız gerektiğini unutmayın .
Steve Rukuts

3
Bu giriş akışı http bağlantısından alınmışsa, tamponlanacak ve indirilecek ve ardından kaynaktaki tüm baytları yazacak mı ?????
dbw

2
Akış kullandığımda PDF görüntüleyici oluşturdum, akışı bağladıktan sonra ve aynı akışı kullanarak pdf dosyasını kaydettiğimde "Seek (0, SeekOrigin.Begin)" kullanmadan doğru belgeyi kaydedemeyeceğim. "+1 (0, SeekOrigin.Begin)" ifadesinden bahsettiğiniz için +1
user2463514

myOtherObject.InputStream.CopyTo (Filestream); bu satır bir hata veriyor: erişim reddedildi.
sulhadin

2
myOtherObject ??
Harry

531

Sen olmamalıdır kullanmak StreamReader(gif veya jpgs gibi) ikili dosyalar için. StreamReaderiçindir metin verilerinin. Rastgele ikili veriler için kullanırsanız neredeyse kesinlikle veri kaybedersiniz. (Encoding.GetEncoding (28591) kullanıyorsanız muhtemelen iyi olacaksınız, ama ne anlamı var?)

Neden a kullanmanız gerekiyor StreamReader? Neden sadece ikili veriyi ikili veri olarak tutmak ve ikili veri olarak diske (veya SQL) geri yazmak istemiyorsunuz?

DÜZENLEME: Bu eğer ... insanlar görmek istiyorum şey gibi görünüyor gibi do sadece bu gibi kullanım şey (bir dosya örneğin) diğerine akışını kopyalamak istiyorum:

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

Bir akışı bir dosyaya dökmek için kullanmak için, örneğin:

using (Stream file = File.Create(filename))
{
    CopyStream(input, file);
}

Not Stream.CopyTotemelde aynı amaca hizmet, .NET 4'te tanıtıldı.


6
Bu böyle ortak bir durum gibi görünüyor ben onun .NET değil şaşırttı. İnsanların bayt dizileri oluşturduğunu görüyorum ki büyük dosyalarda sorunlara neden olabiliyor.
Tilendor

81
@Tilendor: .NET 4'te bir uzantı yöntemi olarak mevcut. (CopyTo)
Jon Skeet

33
Bunun bir uzantı yöntemi olduğunu düşünmüyorum, ancak Stream sınıfında yeni.
Kugel

9
@Kugel: Haklısın, üzgünüm. Ben bir yardımcı kütüphanesinde bir uzantısı yöntemi olarak vardı, fakat şimdi Çayı'nın kendisi olduğunu, benim uzatma yöntemi denir almaz.
Jon Skeet

4
@Florian: Oldukça keyfi - çok fazla bellek almaktan kaçınmak için yeterince küçük ve bir seferde makul bir yığın aktarmak için yeterince büyük. Belki 16K, 32K olmak iyi olurdu - sadece büyük nesne yığınına son vermemeye dikkat ediyorum.
Jon Skeet

77
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}

28
Muhtemelen streamnesneyi using(){}parantez içine koymamalısınız. Metodunuz akışı yaratmadı, bu yüzden atmamalı.
LarsTech

2
Bunun FileStreamyerine kullanmak yerine koymak gerekir , aksi takdirde çöp toplanana kadar açık tutulur.
Pavel Chikulaev

Yaklaşımınızın WinForms'daki sorunumu AWS S3 sınıfı ağ geçidi sınıfımla çözmek için çok daha yakın olduğunu gördüm! teşekkür ederim!
Luiz Eduardo

2
Bu iyi koştu ama 0 KB çıktı aldım. Bunun yerine doğru çıkış için bunu yapmak zorunda: File.WriteAllBytes(destinationFilePath, input.ToArray());. Benim durumumda, inputbir içinden MemoryStreamgeliyor ZipArchive.
SNag

23
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}

1
Bu iyi koştu ama 0 KB çıktı aldım. Bunun yerine doğru çıkış için bunu yapmak zorunda: File.WriteAllBytes(destinationFilePath, input.ToArray());. Benim durumumda, inputbir içinden MemoryStreamgeliyor ZipArchive.
SNag

2
Bu, neyi yanlış yaptığımı anlamama yardımcı oldu. Ancak, akışın başlangıcına geçmeyi unutmayın: stream.Seek(0, SeekOrigin.Begin);
Nathan Bills

9

CopyToUygulamayı kullanan sistemlerin .NET 4.0+ sürümüne yükseltilmemiş olabileceği tüm yanıtları kullanmıyorum . Bazılarının insanları yükseltmeye zorlamak istediğini biliyorum, ancak uyumluluk da güzel.

Başka bir şey, öncelikle başka bir akıştan kopyalamak için bir akış kullanmıyorum. Neden sadece:

byte[] bytes = myOtherObject.InputStream.ToArray();

Baytları aldıktan sonra, bunları bir dosyaya kolayca yazabilirsiniz:

public static void WriteFile(string fileName, byte[] bytes)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write))
    {
        fs.Write(bytes, 0, (int)bytes.Length);
        //fs.Close();
    }
}

Bu kod bir .jpgdosya ile test ettik gibi çalışır , ancak ben sadece küçük dosyalarla (1 MB'den az) kullandığımı itiraf ediyorum. Bir akış, akışlar arasında kopyalama yok, kodlamaya gerek yok, sadece baytları yazın! StreamReaderZaten bytesdoğrudan dönüştürebileceğiniz bir akışınız varsa, şeyleri aşırı karmaşıklaştırmanıza gerek yok .ToArray()!

Bu şekilde yaparken görebildiğim sadece potansiyel dezavantajları, sahip olduğunuz büyük bir dosya varsa, bir akış olarak sahip olmak .CopyTo()ya da eşdeğerini FileStreamkullanmak, bir bayt dizisi kullanmak ve baytları tek tek okumak yerine onu akışa izin verir . Sonuç olarak, bu şekilde yapmak daha yavaş olabilir. Ancak baytları yazma tutamaçlarının .Write()yöntemi boğulmamalı FileStreamve her seferinde sadece bir bayt yapıyor, bu nedenle belleği tıkamayacak, ancak akışı bir tutmak için yeterli belleğe sahip olmanız gerekecek byte[]nesne . Bunu kullandığım durumumda, bir almak OracleBlobzorunda kaldım, gitmek zorunda kaldım byte[], yeterince küçüktü ve ayrıca, yine de benim için kullanılabilir bir akış yoktu, bu yüzden baytlarımı yukarıdaki işleve gönderdim.

Bir akışı kullanan başka bir seçenek, onu CopyStreambaşka bir gönderideki Jon Skeet işleviyle kullanmak olacaktır - bu sadece FileStreamgiriş akışını almak ve dosyayı doğrudan oluşturmak için kullanır . Kullanamadığı File.Creategibi kullanmaz (başlangıçta benim için sorunlu görünüyordu, ancak daha sonra muhtemelen sadece bir VS hatası olduğunu buldu ...).

/// <summary>
/// Copies the contents of input to output. Doesn't close either stream.
/// </summary>
public static void CopyStream(Stream input, Stream output)
{
    byte[] buffer = new byte[8 * 1024];
    int len;
    while ( (len = input.Read(buffer, 0, buffer.Length)) > 0)
    {
        output.Write(buffer, 0, len);
    }    
}

public static void WriteFile(string fileName, Stream inputStream)
{
    string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
    if (!path.EndsWith(@"\")) path += @"\";

    if (File.Exists(Path.Combine(path, fileName)))
        File.Delete(Path.Combine(path, fileName));

    using (FileStream fs = new FileStream(Path.Combine(path, fileName), FileMode.CreateNew, FileAccess.Write)
    {
        CopyStream(inputStream, fs);
    }

    inputStream.Close();
    inputStream.Flush();
}

1
CloseÇünkü aramaya gerek yokusing()
Alex78191

@ Alex78191 Eğer hakkında konuşuyorsanız inputStream.Close(), tekrar bakın - inputStreamdeğişken olarak gönderilir. usingVerildi path+filename, çıkış akımına. Eğer fs.Close()ortada hakkında konuşuyorsanız using, üzgünüm, bu konuda haklıydınız ve ben kaldırdım.
vapcguy

8
//If you don't have .Net 4.0  :)

public void SaveStreamToFile(Stream stream, string filename)
{  
   using(Stream destination = File.Create(filename))
      Write(stream, destination);
}

//Typically I implement this Write method as a Stream extension method. 
//The framework handles buffering.

public void Write(Stream from, Stream to)
{
   for(int a = from.ReadByte(); a != -1; a = from.ReadByte())
      to.WriteByte( (byte) a );
}

/*
Note, StreamReader is an IEnumerable<Char> while Stream is an IEnumbable<byte>.
The distinction is significant such as in multiple byte character encodings 
like Unicode used in .Net where Char is one or more bytes (byte[n]). Also, the
resulting translation from IEnumerable<byte> to IEnumerable<Char> can loose bytes
or insert them (for example, "\n" vs. "\r\n") depending on the StreamReader instance
CurrentEncoding.
*/

16
Akış bayt bayt baytını (ReadByte / WriteByte kullanarak) kopyalamak, arabelleğe kadar arabellek kopyalamaktan çok daha yavaş olacaktır (Read (bayt [], int, int) / Write (bayt [], int, int) kullanarak).
Kevin

6

Neden bir FileStream nesnesi kullanmıyorsunuz?

public void SaveStreamToFile(string fileFullPath, Stream stream)
{
    if (stream.Length == 0) return;

    // Create a FileStream object to write a stream to a file
    using (FileStream fileStream = System.IO.File.Create(fileFullPath, (int)stream.Length))
    {
        // Fill the bytes[] array with the stream data
        byte[] bytesInStream = new byte[stream.Length];
        stream.Read(bytesInStream, 0, (int)bytesInStream.Length);

        // Use FileStream object to write to the specified file
        fileStream.Write(bytesInStream, 0, bytesInStream.Length);
     }
}

46
giriş akışı 1 GB uzunluğundaysa - bu kod 1 GB tampon ayırmaya çalışır :)
Buthrakaur

1
ResponseStream ile çalışmaz, çünkü uknown uzunluğundadır.
Tomas Kubes

Bellek için kullanılabilir olması doğru olsa da byte[], bence bir dosyaya 1 GB + damla akıtabilirsiniz ... DVD torrentleri tutan bir siteniz yoksa ... Artı , çoğu bilgisayar zaten bu gün kullanılabilir en az 2 GB RAM var .... Uyarı geçerlidir, ama bu muhtemelen çoğu iş için "yeterince iyi" bir durum olduğunu düşünüyorum.
vapcguy

Web sitesi aynı anda yalnızca tek bir kullanıcı etkin olmadığı sürece, web sunucuları böyle bir durumu hiç hoş görmeyecektir.
NateTheGreatt

6

Başka bir seçenek de akışı bir a almak byte[]ve kullanmaktır File.WriteAllBytes. Bu şunları yapmalıdır:

using (var stream = new MemoryStream())
{
    input.CopyTo(stream);
    File.WriteAllBytes(file, stream.ToArray());
}

Bir uzantı yöntemiyle sarmak daha iyi adlandırma sağlar:

public void WriteTo(this Stream input, string file)
{
    //your fav write method:

    using (var stream = File.Create(file))
    {
        input.CopyTo(stream);
    }

    //or

    using (var stream = new MemoryStream())
    {
        input.CopyTo(stream);
        File.WriteAllBytes(file, stream.ToArray());
    }

    //whatever that fits.
}

3
Giriş çok büyükse, yetersiz bellek istisnası alırsınız. İçeriği giriş akışından bir
dosya

4
public void testdownload(stream input)
{
    byte[] buffer = new byte[16345];
    using (FileStream fs = new FileStream(this.FullLocalFilePath,
                        FileMode.Create, FileAccess.Write, FileShare.None))
    {
        int read;
        while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
        {
             fs.Write(buffer, 0, read);
        }
    }
}

Tamponlanmış giriş akışını doğrudan FileStream- nice!
vapcguy

3

Uygun kullanımları ve vazgeçilmezlerin uygulanmasını kullanan bir örnek:

static void WriteToFile(string sourceFile, string destinationfile, bool append = true, int bufferSize = 4096)
{
    using (var sourceFileStream = new FileStream(sourceFile, FileMode.OpenOrCreate))
    {
        using (var destinationFileStream = new FileStream(destinationfile, FileMode.OpenOrCreate))
        {
            while (sourceFileStream.Position < sourceFileStream.Length)
            {
                destinationFileStream.WriteByte((byte)sourceFileStream.ReadByte());
            }
        }
    }
}

... ve bu da var

    public static void WriteToFile(FileStream stream, string destinationFile, int bufferSize = 4096, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.ReadWrite)
    {
        using (var destinationFileStream = new FileStream(destinationFile, mode, access, share))
        {
            while (stream.Position < stream.Length) 
            {
                destinationFileStream.WriteByte((byte)stream.ReadByte());
            }
        }
    }

Anahtar, kullanımın doğru kullanımını anlamak (yukarıda gösterildiği gibi vazgeçilmez olan nesnenin somutlaştırılması üzerinde uygulanması gerekir) ve özelliklerin akışlar için nasıl çalıştığı hakkında iyi bir fikre sahip olmaktır. Konum, kelimenin tam anlamıyla, her baytın readbyte yöntemi kullanılarak okunmasıyla izlenen akış içindeki dizindir (0'dan başlar). Bu durumda temelde bir for döngüsü değişkeni yerine kullanıyorum ve basitçe tüm akışın (bayt cinsinden) LITERALLY uzunluğuna kadar devam etmesine izin veriyorum. Baytlar olarak yoksayın çünkü pratik olarak aynıdır ve her şeyi temiz bir şekilde çözen basit ve zarif bir şeye sahip olacaksınız.

Ayrıca, ReadByte yönteminin baytı işlemdeki bir int değerine çevirdiğini ve basitçe geri dönüştürülebileceğini unutmayın.

Son zamanlarda büyük aşırı yüklenmeyi önlemek için sıralı veri yazma sağlamak için dinamik bir tampon oluşturmak için yazdığım başka bir uygulama ekleyeceğim

private void StreamBuffer(Stream stream, int buffer)
{
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        var memoryBuffer = memoryStream.GetBuffer();

        for (int i = 0; i < memoryBuffer.Length;)
        {
            var networkBuffer = new byte[buffer];
            for (int j = 0; j < networkBuffer.Length && i < memoryBuffer.Length; j++)
            {
                networkBuffer[j] = memoryBuffer[i];
                i++;
            }
            //Assuming destination file
            destinationFileStream.Write(networkBuffer, 0, networkBuffer.Length);
        }
    }
}

Açıklama oldukça basittir: yazmak istediğimiz tüm veri kümesini aklımızda tutmamız gerektiğini ve sadece belirli miktarları yazmak istediğimizi biliyoruz, bu yüzden son parametrenin boş olduğu ilk döngüyü istiyoruz (aynı ). Daha sonra, geçirilenlerin boyutuna ayarlanmış bir bayt dizi tamponu başlatırız ve ikinci döngü ile j'yi tamponun boyutu ve orijinalin boyutuyla karşılaştırırız ve orijinalin boyutundan büyükse bayt dizisi, çalıştırmayı sonlandırır.

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.