Bagaimana cara saya menyimpan aliran ke file di C #?

713

Saya memiliki StreamReaderobjek yang saya inisialisasi dengan aliran, sekarang saya ingin menyimpan aliran ini ke disk (aliran mungkin a .gifatau .jpgatau .pdf).

Kode yang ada:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. Saya perlu menyimpan ini ke disk (saya memiliki nama file).
  2. Di masa depan saya mungkin ingin menyimpan ini ke SQL Server.

Saya memiliki tipe penyandian juga, yang akan saya perlukan jika saya menyimpannya ke SQL Server, benar?

Loadman
sumber
1
Apa itu myOtherObject?
anhtv13
2
Masih belum ada jawaban yang diterima untuk pertanyaan ini?
Brett Rigby
@ BrettRigby Ada Jon Skeet Answer, cukup banyak diterima secara otomatis: D
Ricardo Dias Morais

Jawaban:

913

Seperti yang disorot oleh Tilendor dalam jawaban Jon Skeet, stream memiliki CopyTometode sejak .NET 4.

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

Atau dengan usingsintaks:

using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
    myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
    myOtherObject.InputStream.CopyTo(fileStream);
}
Antoine Leclair
sumber
67
Perhatikan bahwa Anda harus menelepon myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)jika Anda belum berada di awal atau Anda tidak akan menyalin seluruh aliran.
Steve Rukuts
3
Jika aliran input ini didapat dari koneksi http maka akankah buffer dan unduh dan kemudian menulis semua byte dari sumber ?????
dbw
2
Saya telah membuat PDF viewer di mana saya menggunakan stream, begitu saya mengikat stream dan ketika saya menyimpan file pdf menggunakan stream yang sama maka tanpa menggunakan "Seek (0, SeekOrigin.Begin)" Saya tidak akan dapat menyimpan dokumen yang benar. jadi +1 untuk menyebut "Seek (0, SeekOrigin.Begin)" ini
user2463514
myOtherObject.InputStream.CopyTo (fileStream); baris ini memberikan kesalahan: akses ditolak.
sulhadin
2
myOtherObject ??
Harry
531

Anda tidak boleh menggunakan StreamReaderfile biner (seperti gifs atau jpgs). StreamReaderadalah untuk data teks . Anda hampir pasti akan kehilangan data jika Anda menggunakannya untuk data biner sewenang-wenang. (Jika Anda menggunakan Encoding.GetEncoding (28591) Anda mungkin akan baik-baik saja, tapi apa gunanya?)

Mengapa Anda perlu menggunakan StreamReadersama sekali? Mengapa tidak hanya menyimpan data biner sebagai data biner dan menulisnya kembali ke disk (atau SQL) sebagai data biner?

EDIT: Karena ini tampaknya menjadi sesuatu yang orang ingin melihat ... jika Anda tidak hanya ingin menyalin satu aliran yang lain (misalnya untuk file) menggunakan sesuatu seperti ini:

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

Untuk menggunakannya untuk membuang aliran ke file, misalnya:

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

Catatan yang Stream.CopyTodiperkenalkan di .NET 4, pada dasarnya melayani tujuan yang sama.

Jon Skeet
sumber
6
Ini seperti kasus umum saya kaget karena tidak ada di. Saya melihat orang yang membuat byte array ukuran seluruh file, yang dapat menyebabkan masalah untuk file besar.
Tilendor
81
@ Tilendor: Hadir sebagai metode ekstensi dalam .NET 4. (CopyTo)
Jon Skeet
33
Saya tidak berpikir itu adalah metode ekstensi, tapi ini baru di kelas Stream.
Kugel
9
@ Kugel: Anda benar, maaf. Saya memilikinya sebagai metode ekstensi di pustaka utilitas, tetapi sekarang karena itu dalam Stream itu sendiri, metode ekstensi saya tidak dipanggil.
Jon Skeet
4
@Florian: Cukup sewenang-wenang - nilai yang cukup kecil untuk menghindari mengambil terlalu banyak memori, dan cukup besar untuk mentransfer bongkahan yang masuk akal pada suatu waktu. Akan lebih baik menjadi 16K, 32K mungkin - saya hanya akan berhati-hati untuk tidak berakhir pada tumpukan objek besar.
Jon Skeet
77
public void CopyStream(Stream stream, string destPath)
{
  using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
  {
    stream.CopyTo(fileStream);
  }
}
Darren Corbett
sumber
28
Anda mungkin tidak harus meletakkan streamobjek di using(){}braket. Metode Anda tidak membuat aliran, jadi itu tidak boleh membuangnya.
LarsTech
2
Alih-alih, Anda harus menggunakannya FileStreamsebagai gantinya, jika tidak maka akan tetap terbuka sampai sampah dikumpulkan.
Pavel Chikulaev
Saya telah menemukan bahwa pendekatan Anda jauh lebih dekat untuk menyelesaikan masalah saya di WinForms dengan kelas gateway kelas AWS S3 saya! Terima kasih!
Luiz Eduardo
2
Ini berjalan dengan baik tetapi saya mendapat output 0 KB. Sebaliknya aku harus melakukan ini untuk output yang benar: File.WriteAllBytes(destinationFilePath, input.ToArray());. Dalam kasus saya, inputapakah MemoryStreamdatang dari dalam a ZipArchive.
SNag
23
private void SaveFileStream(String path, Stream stream)
{
    var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
    stream.CopyTo(fileStream);
    fileStream.Dispose();
}
jhonjairoroa87
sumber
1
Ini berjalan dengan baik tetapi saya mendapat output 0 KB. Sebaliknya aku harus melakukan ini untuk output yang benar: File.WriteAllBytes(destinationFilePath, input.ToArray());. Dalam kasus saya, inputapakah MemoryStreamdatang dari dalam a ZipArchive.
SNag
2
Ini membantu saya mencari tahu apa yang saya lakukan salah. Namun, jangan lupa untuk pindah ke awal aliran: stream.Seek(0, SeekOrigin.Begin);
Nathan Bills
9

Saya tidak mendapatkan semua jawaban menggunakan CopyTo, di mana mungkin sistem yang menggunakan aplikasi mungkin belum ditingkatkan ke .NET 4.0+. Saya tahu beberapa orang ingin memaksa orang untuk melakukan upgrade, tetapi kompatibilitasnya juga bagus.

Hal lain, saya tidak bisa menggunakan aliran untuk menyalin dari aliran lain di tempat pertama. Mengapa tidak lakukan saja:

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

Setelah Anda memiliki byte, Anda dapat dengan mudah menuliskannya ke file:

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

Kode ini berfungsi saat saya mengujinya dengan a .jpg file, meskipun saya akui saya hanya menggunakannya dengan file kecil (kurang dari 1 MB). Satu aliran, tidak ada penyalinan antar aliran, tidak perlu penyandian, cukup tulis byte! Tidak perlu terlalu rumit hal-hal dengan StreamReaderjika Anda sudah memiliki aliran Anda dapat mengonversi bytessecara langsung dengan .ToArray()!

Hanya kerugian potensial yang dapat saya lihat dengan melakukannya dengan cara ini adalah jika ada file besar yang Anda miliki, menjadikannya sebagai stream dan menggunakan .CopyTo()atau setara memungkinkan FileStreamuntuk streaming itu daripada menggunakan array byte dan membaca byte satu per satu. Mungkin lebih lambat melakukannya dengan cara ini, sebagai hasilnya. Tetapi seharusnya tidak tersedak karena .Write()metode FileStreampegangan menulis byte, dan itu hanya melakukannya satu byte pada satu waktu, sehingga tidak akan menyumbat memori, kecuali bahwa Anda harus memiliki cukup memori untuk menahan aliran sebagai byte[]objek . Dalam situasi saya di mana saya menggunakan ini, mendapatkan OracleBlob, saya harus pergi ke byte[], itu cukup kecil, dan lagi pula, tidak ada streaming yang tersedia untuk saya, jadi saya hanya mengirim byte saya ke fungsi saya, di atas.

Pilihan lain, menggunakan aliran, akan menggunakannya dengan CopyStreamfungsi Jon Skeet yang ada di pos lain - ini hanya menggunakan FileStreamuntuk mengambil aliran input dan membuat file dari itu secara langsung. Itu tidak digunakan File.Create, seperti yang dia lakukan (yang awalnya tampak bermasalah bagi saya, tetapi kemudian menemukan itu kemungkinan hanya bug VS ...).

/// <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();
}
vapcguy
sumber
1
Tidak perlu menelepon Closekarenausing()
Alex78191
@ Alex78191 Jika Anda berbicara tentang inputStream.Close(), lihat lagi - inputStreamdikirim sebagai variabel. Ada usingpada path+filenamealiran output. Jika Anda berbicara tentang fs.Close()di tengah-tengah using, maaf, Anda benar tentang itu dan saya menghapusnya.
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.
*/
George
sumber
16
Menyalin stream byte-by-byte (menggunakan ReadByte / WriteByte) akan jauh lebih lambat daripada menyalin buffer-by-buffer (menggunakan Read (byte [], int, int) / Write (byte [], int, int)).
Kevin
6

Mengapa tidak menggunakan objek FileStream?

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);
     }
}
Adrian
sumber
46
bagaimana jika input stream panjangnya 1GB - kode ini akan mencoba mengalokasikan buffer 1GB :)
Buthrakaur
1
Ini tidak bekerja dengan ResponseStream, karena panjangnya cukup lama.
Tomas Kubes
Meskipun benar Anda harus memiliki memori yang tersedia untuk byte[], saya pikir itu akan jarang bahwa Anda akan streaming 1 GB + gumpalan ke file ... kecuali jika Anda memiliki situs yang menyimpan DVD torrents ... Plus , kebanyakan komputer memiliki setidaknya 2 GB RAM yang tersedia hari ini, toh ... Peringatan itu valid, tapi saya pikir ini adalah kasus di mana itu mungkin "cukup baik" untuk sebagian besar pekerjaan.
vapcguy
Webservers tidak akan mentolerir kasus seperti ini dengan baik sama sekali, kecuali situs web hanya memiliki satu pengguna yang aktif sekaligus.
NateTheGreatt
6

Pilihan lain adalah untuk mendapatkan aliran ke byte[]dan gunakan File.WriteAllBytes. Ini harus dilakukan:

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

Membungkusnya dengan metode ekstensi memberikan penamaan yang lebih baik:

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.
}
nawfal
sumber
3
Jika input terlalu besar Anda akan mendapatkan pengecualian memori. Pilihan untuk menyalin konten dari aliran input ke streaming film jauh lebih baik
Ykok
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);
        }
    }
}
Angelo
sumber
Menyediakan aliran input yang disangga secara langsung ke FileStream- nice!
vapcguy
3

Berikut adalah contoh yang menggunakan penggunaan yang tepat dan implementasi idisposable:

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

... dan ada juga ini

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

Kuncinya adalah memahami penggunaan yang tepat dari penggunaan (yang harus diimplementasikan pada instantiasi objek yang mengimplementasikan idisposable seperti yang ditunjukkan di atas), dan memiliki ide yang bagus tentang bagaimana properti bekerja untuk stream. Posisi secara harfiah adalah indeks dalam aliran (yang dimulai dari 0) yang diikuti setiap byte dibaca menggunakan metode readbyte. Dalam hal ini saya pada dasarnya menggunakannya sebagai pengganti variabel loop dan hanya membiarkannya mengikuti sampai ke panjang yang pada akhirnya adalah akhir dari seluruh aliran (dalam byte). Abaikan dalam byte karena praktis sama dan Anda akan memiliki sesuatu yang sederhana dan elegan seperti ini yang menyelesaikan semuanya dengan bersih.

Perlu diingat juga, bahwa metode ReadByte hanya membuang byte ke int dalam proses dan hanya dapat dikonversi kembali.

Saya akan menambahkan implementasi lain yang baru-baru ini saya tulis untuk membuat semacam buffer dinamis untuk memastikan data berurutan menulis untuk mencegah kelebihan besar

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

Penjelasannya cukup sederhana: kita tahu bahwa kita perlu mengingat seluruh rangkaian data yang ingin kita tulis dan juga kita hanya ingin menulis jumlah tertentu, jadi kita ingin loop pertama dengan parameter terakhir kosong (sama seperti saat ). Selanjutnya, kita menginisialisasi buffer array byte yang diatur ke ukuran yang dilewati, dan dengan loop kedua kita membandingkan j dengan ukuran buffer dan ukuran yang asli, dan jika itu lebih besar dari ukuran aslinya array byte, akhiri prosesnya.

Andrew Ramshaw
sumber