如何将流保存到C#文件中?


713

我有一个StreamReader使用流初始化的对象,现在我想将此流保存到磁盘(该流可以是.gifor .jpg或or .pdf)。

现有代码:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
  1. 我需要将其保存到磁盘(我有文件名)。
  2. 将来我可能要将其存储到SQL Server。

我也有编码类型,如果将其存储到SQL Server,我将需要它,对吗?


1
什么是myOtherObject?
anhtv13 '19

2
这个问题仍然没有被接受的答案吗?
布雷特·里格比

@BrettRigby有一个Jon Skeet答案,它几乎是自动接受的:D
Ricardo Dias Morais

Answers:


912

正如Tiledor在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);
}

66
请注意,myOtherObject.InputStream.Seek(0, SeekOrigin.Begin)如果您还没有开始,则必须打电话,否则您将不会复制整个流。
Steve Rukuts 2012年

3
如果此输入流是从http连接获取的,那么它将缓冲并下载,然后从源写入所有字节????
dbw 2014年

2
我已在要使用流的位置创建了PDF查看器,一旦我绑定了流,并且当我使用相同的流保存pdf文件时却不使用“ Seek(0,SeekOrigin.Begin)”,则将无法保存正确的文档。因此+1提及此“ Seek(0,SeekOrigin.Begin)”
user2463514 2014年

myOtherObject.InputStream.CopyTo(fileStream); 此行给出一个错误:访问被拒绝。
sulhadin

2
myOtherObject ??
哈利

531

不能使用StreamReader二进制文件(如gif或JPG格式)。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 2010年

81
@Tilendor:它是.NET 4中的扩展方法。(CopyTo)
Jon Skeet 2010年

33
我不认为这是扩展方法,但它是Stream类中的新功能。
Kugel

9
@Kugel:对,对不起。在实用程序库中将它作为扩展方法使用,但是现在它在Stream本身中,所以我的扩展方法没有被调用。
乔恩·斯基特

4
@Florian:这是合理的-足够小的值以避免占用过多的内存,并且足够大以一次传输合理的块。最好是16K,也可以是32K-我只是要小心不要最终落在大对象堆上。
乔恩·斯基特

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 2014年

我发现您的方法更接近于使用AWS S3类网关类解决WinForms中的问题!谢谢!
Luiz Eduardo

2
运行得很好,但我得到了0 KB的输出。相反,我必须这样做以获取正确的输出:File.WriteAllBytes(destinationFilePath, input.ToArray());。就我而言,inputMemoryStream来自的内部ZipArchive
SNag '16

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());。就我而言,inputMemoryStream来自的内部ZipArchive
SNag '16

2
这有助于我弄清楚我做错了什么。但是,不要忘了移至视频流的开头: stream.Seek(0, SeekOrigin.Begin);
Nathan Bills

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,2015年

@ 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)要比逐字节复制缓冲区(使用Read(byte [],int,int)/ Write(byte [],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 2011年

1
这不适用于ResponseStream,因为它的长度未知。
Tomas Kubes

确实,您必须拥有可用的内存byte[],但我认为将1 GB +的blob流传输到文件中的情况很少见……除非您有一个站点可以保存DVD种子...否则,无论如何,大多数计算机这些天至少都具有2 GB的RAM。Caveat是有效的,但我认为在这种情况下,它对于大多数工作来说可能“足够好”。
vapcguy

除非网站一次只有一个活动用户,否则网络服务器完全不能容忍这种情况。
NateTheGreatt

6

另一个选择是将流转到a 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
如果输入太大,则会出现内存不足异常。将内容从输入流复制到文件流的选项要好得多
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-不错!
vapcguy '16

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

关键是要了解use的正确用法(应在实现如上所示的实现对象的实例上实现),并对属性如何用于流有个好主意。Position实际上是流中的索引(从0开始),使用readbyte方法读取每个字节时遵循该索引。在这种情况下,我实际上是在用它代替for循环变量,只是简单地让它一直跟踪直到整个流的末尾(以字节为单位)的长度。忽略字节,因为它实际上是相同的,并且您将拥有简单而优雅的东西,可以清晰地解析所有内容。

同样要记住,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);
        }
    }
}

解释非常简单:我们知道我们需要牢记要写入的整个数据集,并且我们只想写入一定数量,因此我们希望第一个循环的最后一个参数为空(与while )。接下来,我们初始化一个字节数组缓冲区,将其设置为所传递的大小,然后在第二个循环中,将j与缓冲区的大小以及原始缓冲区的大小进行比较,如果它大于原始缓冲区的大小,则对其进行比较。字节数组,结束运行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.