关于.net:如何在C#中将流保存到文件?

How do I save a stream to a file in C#?

我有一个用流初始化的StreamReader对象,现在我想把这个流保存到磁盘上(流可以是.gif.jpg.pdf)。

现有代码:

1
StreamReader sr = new StreamReader(myOtherObject.InputStream);
  • 我需要把这个保存到磁盘(我有文件名)。
  • 将来我可能希望将此存储到SQL Server。
  • 我也有编码类型,如果我将其存储到SQL Server,我需要这种类型,对吗?


    正如Tilendor在jon skeet的答案中所强调的,streams自.net 4以来有一个CopyTo方法。

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

    或者使用using语法:

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


    不能将StreamReader用于二进制文件(如gifs或jpgs)。StreamReader用于文本数据。如果将数据用于任意二进制数据,几乎肯定会丢失数据。(如果您使用encoding.getencoding(28591),您可能会没事的,但要点是什么?)

    为什么你需要使用StreamReader?为什么不保留二进制数据作为二进制数据,并将其作为二进制数据写回磁盘(或SQL)?

    编辑:这似乎是人们想要看到的…如果您只想将一个流复制到另一个流(例如,复制到一个文件),请使用如下方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /// <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);
        }    
    }

    要使用它将流转储到文件,例如:

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

    注意,Stream.CopyTo是在.NET 4中引入的,其作用基本相同。


    1
    2
    3
    4
    5
    6
    7
    public void CopyStream(Stream stream, string destPath)
    {
      using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write))
      {
        stream.CopyTo(fileStream);
      }
    }


    1
    2
    3
    4
    5
    6
    private void SaveFileStream(String path, Stream stream)
    {
        var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
        stream.CopyTo(fileStream);
        fileStream.Dispose();
    }


    我并没有用CopyTo得到所有的答案,因为使用该应用程序的系统可能没有升级到.NET 4.0+。我知道有些人想强迫人们升级,但是兼容性也很好。

    另一件事是,我一开始不会使用流从另一个流复制。为什么不直接做:

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

    一旦有了字节,就可以轻松地将它们写入文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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();
        }
    }

    虽然我承认我只在小文件(小于1 MB)中使用过,但这段代码的工作原理与我用.jpg文件测试过的代码相同。一个流,不在流之间复制,不需要编码,只需写入字节!不必过分复杂的事情与StreamReader,如果你已经有一个流,你可以转换为bytes直接与.ToArray()

    这样做的唯一潜在缺点是,如果有一个大文件,将其作为流,并使用.CopyTo()或等效文件,则允许FileStream对其进行流式处理,而不是使用字节数组并逐个读取字节。因此,这样做可能会慢一些。但是它不应该阻塞,因为FileStream.Write()方法处理写入字节,并且一次只执行一个字节,因此它不会阻塞内存,除非您必须有足够的内存来将流作为byte[]对象保存。在我使用这个的情况下,我得到一个OracleBlob,我不得不去一个byte[],它足够小,而且,我没有可用的流,所以我只是把字节发送到我的函数,上面。

    另一种选择是,使用流,将它与另一个post中的jon skeet的CopyStream函数一起使用——这只是使用FileStream获取输入流并直接从中创建文件。它不像他那样使用File.Create(最初对我来说似乎有问题,但后来发现它可能只是一个vs-bug…)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /// <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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    //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,"
    " vs."

    ") depending on the StreamReader instance
    CurrentEncoding.
    */


    为什么不使用文件流对象?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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);
         }
    }


    另一种选择是将流获取到byte[]并使用File.WriteAllBytes。应该这样做:

    1
    2
    3
    4
    5
    using (var stream = new MemoryStream())
    {
        input.CopyTo(stream);
        File.WriteAllBytes(file, stream.ToArray());
    }

    将其包装在扩展方法中可以使其更好地命名:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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.
    }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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);
            }
        }
    }