ฉันจะบันทึกสตรีมไปยังไฟล์ใน C # ได้อย่างไร


713

ฉันมีStreamReaderวัตถุที่ฉันเริ่มต้นกับกระแสตอนนี้ฉันต้องการบันทึกกระแสข้อมูลนี้ลงดิสก์ (กระแสข้อมูลอาจเป็น.gifหรือ.jpgหรือ.pdf)

รหัสที่มีอยู่:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. ฉันต้องการบันทึกสิ่งนี้ลงดิสก์ (ฉันมีชื่อไฟล์)
  2. ในอนาคตฉันอาจต้องการเก็บสิ่งนี้ไว้ใน SQL Server

ฉันมีประเภทการเข้ารหัสด้วยซึ่งฉันจะต้องใช้ถ้าฉันเก็บไว้ใน SQL Server ถูกต้องหรือไม่


1
myOtherObject คืออะไร
anhtv13

2
ยังไม่มีคำตอบที่ยอมรับสำหรับคำถามนี้?
Brett Rigby

@BrettRigby มีคำตอบ Jon Skeet เป็นที่ยอมรับอัตโนมัติมาก: D
Ricardo Dias Morais

คำตอบ:


912

ตามที่ไฮไลต์โดย Tilendor ในคำตอบของ Jon Skeet สตรีมมีCopyToวิธีการตั้งแต่. NET 4

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

หรือด้วยusingไวยากรณ์:

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)หากคุณยังไม่ได้เริ่มต้นมิฉะนั้นคุณจะไม่คัดลอกสตรีมทั้งหมด
Steve Rukuts

3
หากอินพุตสตรีมนี้ได้รับจากการเชื่อมต่อ http ก็จะบัฟเฟอร์และดาวน์โหลดแล้วเขียนไบต์ทั้งหมดจากแหล่งที่มา ?????
dbw

2
ฉันได้สร้างโปรแกรมดู PDF ที่ฉันใช้กระแสข้อมูลเมื่อฉันผูกกระแสและเมื่อฉันบันทึกไฟล์ pdf โดยใช้กระแสเดียวกันโดยไม่ใช้ "Seek (0, SeekOrigin.Begin)" ฉันจะไม่สามารถบันทึกเอกสารที่ถูกต้องได้ ดังนั้น +1 สำหรับการกล่าวถึง "Seek (0, SeekOrigin.Begin)"
user2463514

myOtherObject.InputStream.CopyTo (FileStream); บรรทัดนี้ให้ข้อผิดพลาด: การเข้าถึงถูกปฏิเสธ
sulhadin

2
myOtherObject ??
แฮร์รี่

531

คุณต้องไม่ใช้StreamReaderสำหรับไฟล์ไบนารี (เช่น gifs หรือ jpgs) StreamReaderสำหรับข้อมูลตัวอักษร คุณจะสูญเสียข้อมูลเกือบแน่นอนถ้าคุณใช้สำหรับข้อมูลไบนารีโดยพลการ (ถ้าคุณใช้ Encoding.GetEncoding (28591) คุณอาจจะโอเค แต่ประเด็นคืออะไร)

ทำไมคุณต้องใช้ a StreamReaderทั้งหมด? ทำไมไม่เพียงแค่เก็บข้อมูลไบนารี่เป็นข้อมูลไบนารี่และเขียนมันกลับไปที่ดิสก์ (หรือ SQL) เป็นข้อมูลไบนารี่?

แก้ไข: ในฐานะที่นี้น่าจะเป็นสิ่งที่คนต้องการที่จะเห็น ... ถ้าคุณไม่ต้องการเพียงแค่การคัดลอกสตรีมไปยังอีก (เช่นไฟล์) สิ่งที่ใช้เช่นนี้

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

หากต้องการใช้เพื่อดัมพ์สตรีมไปยังไฟล์ตัวอย่างเช่น:

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

โปรดทราบว่าStream.CopyToมีการนำมาใช้ใน. NET 4 โดยมีจุดประสงค์เดียวกัน


6
ดูเหมือนว่าเป็นกรณีทั่วไปฉันประหลาดใจที่ไม่ได้อยู่ใน. NET ฉันเห็นคนที่สร้างอาร์เรย์ไบต์ขนาดของไฟล์ทั้งหมดซึ่งอาจทำให้เกิดปัญหาสำหรับไฟล์ขนาดใหญ่
Tilendor

81
@Tilendor: มีอยู่เป็นวิธีการขยายใน. NET 4 (CopyTo)
Jon Skeet

33
ฉันไม่คิดว่ามันเป็นวิธีการขยาย แต่มันใหม่ในชั้นเรียนสตรีม
Kugel

9
@Kugel: ถูกต้องขอโทษ ฉันมีมันเป็นวิธีการขยายในห้องสมุดสาธารณูปโภค แต่ตอนนี้มันอยู่ใน Stream เองวิธีการขยายของฉันไม่ได้รับ
Jon Skeet

4
@ ฟลอเรียน: มันมีเหตุผลพอสมควร - มีค่าน้อยพอที่จะหลีกเลี่ยงการใช้หน่วยความจำมากเกินไปและใหญ่พอที่จะถ่ายโอนชิ้นข้อมูลได้ในเวลาเดียวกัน มันคงจะโอเคที่จะเป็น 16K, 32K บางที - ฉันแค่ระวังไม่ให้ท้ายกองวัตถุขนาดใหญ่
Jon Skeet

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

28
คุณอาจไม่ควรวางstreamวัตถุไว้ในusing(){}วงเล็บ วิธีการของคุณไม่ได้สร้างกระแสดังนั้นจึงไม่ควรกำจัดมัน
LarsTech

2
คุณต้องใส่FileStreamแทนเพื่อใช้มิฉะนั้นจะถูกเก็บไว้เปิดจนกว่าจะมีการรวบรวมขยะ
Pavel Chikulaev

ฉันพบว่าวิธีการของคุณเข้าใกล้มากขึ้นเพื่อแก้ปัญหาของฉันใน WinForms ด้วยคลาสเกตเวย์ AWS S3 ของฉัน! ขอบคุณ!
Luiz Eduardo

2
สิ่งนี้ทำงานได้ดี แต่ฉันได้ผลลัพธ์ 0 KB แต่ฉันต้องทำเพื่อผลลัพธ์ที่ถูกต้อง: File.WriteAllBytes(destinationFilePath, input.ToArray());. ในกรณีของผมinputเป็นมาจากภายในMemoryStream 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
สิ่งนี้ทำงานได้ดี แต่ฉันได้ผลลัพธ์ 0 KB แต่ฉันต้องทำเพื่อผลลัพธ์ที่ถูกต้อง: File.WriteAllBytes(destinationFilePath, input.ToArray());. ในกรณีของผมinputเป็นมาจากภายในMemoryStream ZipArchive
SNag

2
สิ่งนี้ช่วยให้ฉันทราบว่าฉันทำอะไรผิด อย่างไรก็ตามอย่าลืมที่จะย้ายไปที่จุดเริ่มต้นของกระแส: stream.Seek(0, SeekOrigin.Begin);
นาธานตั๋วเงิน

9

ฉันไม่ได้รับคำตอบทั้งหมดที่ใช้CopyToซึ่งบางทีระบบที่ใช้แอพอาจไม่ได้รับการอัพเกรดเป็น. NET 4.0+ ฉันรู้ว่าบางคนต้องการบังคับให้คนอัปเกรด แต่ความเข้ากันได้ก็ดีเช่นกัน

อีกอย่างฉันไม่ได้ใช้สตรีมเพื่อคัดลอกจากสตรีมอื่นในตอนแรก ทำไมไม่ทำ:

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

เมื่อคุณมีไบต์คุณสามารถเขียนลงไฟล์ได้อย่างง่ายดาย:

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

รหัสนี้ใช้งานได้ในขณะที่ฉันทดสอบด้วย.jpgไฟล์แม้ว่าฉันจะยอมรับว่าฉันใช้กับไฟล์ขนาดเล็กเท่านั้น (น้อยกว่า 1 MB) สตรีมเดียวไม่มีการคัดลอกระหว่างสตรีมไม่จำเป็นต้องเข้ารหัสเพียงเขียนไบต์! ไม่จำเป็นต้องทำสิ่งที่ยุ่งยากเกินไปStreamReaderหากคุณมีกระแสคุณสามารถแปลงเป็นbytesโดยตรงด้วย.ToArray()!

ข้อเสียที่เป็นไปได้เท่านั้นที่ฉันเห็นในการทำเช่นนี้คือถ้าคุณมีไฟล์ขนาดใหญ่การใช้เป็นสตรีมและการใช้.CopyTo()หรือเทียบเท่าอนุญาตให้FileStreamสตรีมแทนการใช้อาร์เรย์ไบต์และอ่านไบต์ทีละรายการ อาจทำให้การทำแบบนี้ช้าลง แต่มันไม่ควรทำให้หายใจไม่ออกเนื่องจาก.Write()วิธีการของการFileStreamจัดการการเขียนไบต์และมันทำเพียงหนึ่งไบต์ในแต่ละครั้งดังนั้นมันจะไม่เกิดการอุดตันหน่วยความจำยกเว้นว่าคุณจะต้องมีหน่วยความจำเพียงพอที่จะถือกระแสเป็นbyte[]วัตถุ ในสถานการณ์ของฉันที่ฉันใช้สิ่งนี้การรับOracleBlobฉันต้องไปbyte[]มันเล็กพอและนอกจากนี้ไม่มีการส่งกระแสข้อมูลให้ฉันดังนั้นฉันเพิ่งส่งไบต์ไปยังฟังก์ชันของฉันข้างต้น

ตัวเลือกอื่นที่ใช้สตรีมคือใช้กับCopyStreamฟังก์ชั่นของ Jon Skeet ที่อยู่ในโพสต์อื่น - นี่ใช้FileStreamเพื่อรับสตรีมอินพุตและสร้างไฟล์จากมันโดยตรง มันไม่ได้ใช้งานFile.Createเหมือนที่เขาทำ (ซึ่งตอนแรกดูเหมือนจะเป็นปัญหาสำหรับฉัน แต่ภายหลังพบว่ามันอาจเป็นเพียงข้อผิดพลาด 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();
}

1
ไม่จำเป็นต้องโทรCloseเพราะusing()
Alex78191

@ Alex78191 หากคุณกำลังพูดถึงinputStream.Close()ให้ดูอีกครั้ง - inputStreamถูกส่งเป็นตัวแปร usingอยู่ในpath+filenameกระแสออก หากคุณกำลังพูดถึงfs.Close()กลางusingขออภัยคุณถูกต้องเกี่ยวกับเรื่องนั้นและฉันลบออก
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
การคัดลอกสตรีมไบต์ต่อไบต์ (โดยใช้ ReadByte / WriteByte) จะช้ากว่าการคัดลอกบัฟเฟอร์ต่อบัฟเฟอร์ (ใช้อ่าน (ไบต์ [], int, int) / เขียน (ไบต์ [], int, int))
เควิน

6

ทำไมไม่ใช้วัตถุ 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);
     }
}

46
จะเกิดอะไรขึ้นถ้าสตรีมอินพุทยาว 1GB - รหัสนี้จะพยายามจัดสรรบัฟเฟอร์ 1GB :)
Buthrakaur

1
สิ่งนี้ไม่ทำงานกับ ResponseStream เพราะมีความยาวเท่าไร
Tomas Kubes

ในขณะที่มันเป็นความจริงที่คุณจะต้องมีหน่วยความจำสำหรับbyte[]ฉันคิดว่ามันจะยากที่คุณจะสตรีม 1 GB + หยดไปยังไฟล์ ... เว้นแต่คุณจะมีเว็บไซต์ที่เก็บ torrents ดีวีดี ... เพิ่มเติม คอมพิวเตอร์ส่วนใหญ่มี RAM อย่างน้อย 2 GB ในวันนี้อย่างไรก็ตาม .... Caveat ใช้ได้ แต่ฉันคิดว่านี่เป็นกรณีที่อาจจะ "ดีพอ" สำหรับงานส่วนใหญ่
vapcguy

Webservers จะไม่ยอมให้กรณีเช่นนี้เป็นอย่างดีเลยเว้นแต่เว็บไซต์จะมีผู้ใช้เพียงคนเดียวที่ใช้งานได้ในครั้งเดียว
NateTheGreatt

6

อีกตัวเลือกหนึ่งคือการได้รับกระแสไปและการใช้งานbyte[] File.WriteAllBytesสิ่งนี้ควรทำ:

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

การห่อด้วยวิธีส่วนขยายทำให้การตั้งชื่อดีขึ้น:

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
หากอินพุตมีขนาดใหญ่เกินไปคุณจะได้รับการยกเว้นหน่วยความจำไม่เพียงพอ ตัวเลือกในการคัดลอกเนื้อหาจากอินพุตสตรีมไปยัง filestream นั้นดีกว่ามาก
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);
        }
    }
}

จัดหาสตรีมอินพุตบัฟเฟอร์โดยตรงไปยังFileStream- nice!
vapcguy

3

นี่คือตัวอย่างที่ใช้การใช้และการใช้ 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());
            }
        }
    }
}

... และยังมีสิ่งนี้

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

กุญแจสำคัญคือการเข้าใจการใช้งานที่เหมาะสม (ซึ่งควรนำมาใช้ในการสร้างอินสแตนซ์ของวัตถุที่ใช้ idisposable ดังที่แสดงด้านบน) และมีความคิดที่ดีเกี่ยวกับวิธีการทำงานของคุณสมบัติสำหรับสตรีม ตำแหน่งคือดัชนีภายในสตรีม (ซึ่งเริ่มต้นที่ 0) ตามด้วยแต่ละไบต์จะอ่านโดยใช้วิธี readbyte ในกรณีนี้ฉันใช้มันแทนตัวแปร for loop และปล่อยให้มันติดตามไปจนถึงความยาวซึ่งเป็นจุดสิ้นสุดของสตรีมทั้งหมด (เป็นไบต์) ไม่ต้องสนใจไบต์เพราะมันเหมือนกันและคุณจะมีสิ่งที่เรียบง่ายและสง่างามเช่นนี้ที่แก้ไขทุกสิ่งได้อย่างหมดจด

โปรดจำไว้ว่าวิธี ReadByte จะแปลงไบต์ไปเป็น int ในกระบวนการและสามารถแปลงกลับได้

ฉันจะเพิ่มการใช้งานอื่นที่ฉันเพิ่งเขียนเพื่อสร้างบัฟเฟอร์แบบไดนามิกเพื่อให้แน่ใจว่าการเขียนข้อมูลตามลำดับเพื่อป้องกันการโอเวอร์โหลดขนาดใหญ่

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

คำอธิบายนั้นค่อนข้างง่าย: เรารู้ว่าเราจำเป็นต้องคำนึงถึงชุดข้อมูลทั้งหมดที่เราต้องการจะเขียนและเราต้องการเขียนจำนวนที่แน่นอนเท่านั้นดังนั้นเราต้องการให้ลูปแรกที่มีพารามิเตอร์สุดท้ายว่างเปล่า (เช่นเดียวกับในขณะที่ ) ต่อไปเราจะทำการ initialize buffer byte array ที่กำหนดขนาดของสิ่งที่ผ่านไปและด้วย loop ที่สองเราเปรียบเทียบ j กับขนาดของ buffer และขนาดของ buffer เดิมและถ้ามันใหญ่กว่าขนาดเดิม อาร์เรย์ไบต์สิ้นสุดการรัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.