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

Loa*_*man 679 .net c# stream

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

现有守则:

StreamReader sr = new StreamReader(myOtherObject.InputStream);
Run Code Online (Sandbox Code Playgroud)
  1. 我需要将其保存到磁盘(我有文件名).
  2. 在将来,我可能希望将其存储到SQL Server.

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

Ant*_*air 876

正如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();
Run Code Online (Sandbox Code Playgroud)

或者使用using语法:

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

  • 请注意,如果您还没有在开头,则必须调用`myOtherObject.InputStream.Seek(0,SeekOrigin.Begin)`或者您不会复制整个流. (62认同)
  • 如果此输入流是从http连接获得的,那么它将缓冲并下载然后从源写入所有字节????? (3认同)
  • 我创建了PDF查看器,我正在使用流,一旦我绑定流,当我使用相同的流保存pdf文件,然后不使用"Seek(0,SeekOrigin.Begin)"我将无法保存正确的文档.所以+1提到这个"寻求(0,SeekOrigin.Begin)" (2认同)

Jon*_*eet 514

不能使用StreamReader二进制文件(如gifs或jpgs).StreamReader用于文本数据.如果将它用于任意二进制数据,几乎肯定会丢失数据.(如果你使用Encoding.GetEncoding(28591),你可能会没事,但重点是什么?)

为什么你需要使用一个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);
    }    
}
Run Code Online (Sandbox Code Playgroud)

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

using (Stream file = File.Create(filename))
{
    CopyStream(input, file);
}
Run Code Online (Sandbox Code Playgroud)

请注意,这Stream.CopyTo是在.NET 4中引入的,基本上用于相同的目的.

  • @Tilendor:它作为.NET 4中的扩展方法存在.(CopyTo) (76认同)
  • 我不认为它是一种扩展方法,但它是Stream类中的新功能. (29认同)
  • @Kugel:你是对的,对不起.*我*将它作为实用程序库中的扩展方法,但现在它在Stream本身,我的扩展方法不会被调用. (8认同)
  • 这似乎是一种常见的情况,我很惊讶它不在.NET中.我看到人们创建的字节数组大小与整个文件大小相同,这可能会导致大文件出现问题. (6认同)
  • @Florian:它是相当随意的 - 一个足够小的值可以避免占用太多内存,而且足够大,可以一次传输一个合理的块.可能是16K,32K可能 - 我只是小心不要在大对象堆上结束. (4认同)
  • 为什么要将缓冲区的大小设置为8192?谢谢 (2认同)

Dar*_*ett 72

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

  • 你可能不应该把`stream`对象放在`using(){}`括号中.您的方法没有创建流,因此不应该将其丢弃. (25认同)
  • 相反,您需要将`FileStream`改为使用,否则它将保持打开状态,直到它被垃圾收集. (2认同)
  • 这运行良好,但我得到了 0 KB 输出。相反,我必须为正确的输出执行此操作:`File.WriteAllBytes(destinationFilePath, input.ToArray());`。在我的例子中,`input` 是一个来自 `ZipArchive` 的 `MemoryStream`。 (2认同)

小智 22

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

  • 这帮我弄明白我做错了什么.但是,不要忘记移动到流的开头:`stream.Seek(0,SeekOrigin.Begin);` (2认同)

vap*_*guy 9

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

另一件事,我没有首先使用流从另一个流复制.为什么不这样做:

byte[] bytes = myOtherObject.InputStream.ToArray();
Run Code Online (Sandbox Code Playgroud)

获得字节后,您可以轻松地将它们写入文件:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这段代码可以正常运行,因为我已经用.jpg文件对它进行了测试,但我承认我只使用了小文件(小于1 MB).一个流,流之间没有复制,不需要编码,只需写入字节!StreamReader如果您已经拥有可以bytes直接转换为的流,则无需过度复杂化.ToArray()!

我只能通过这种方式看到的潜在缺点是,如果有一个大文件,将其作为流使用.CopyTo()或使用或等效允许FileStream流式传输而不是使用字节数组并逐个读取字节.结果这样做可能会慢一些.但它不应该因为句柄的.Write()方法FileStream写入字节而窒息,并且它一次只做一个字节,所以它不会阻塞内存,除了你必须有足够的内存来保存流作为一个byte[]对象.在我使用它的情况下,获得一个OracleBlob,我必须去一个byte[],它足够小,而且,无论如何,我没有可用的流,所以我只是把我的字节发送到我的函数,上面.

使用流的另一个选择是将它与Jon Skeet的CopyStream功能一起使用,该功能位于另一个帖子中 - 这只是FileStream用来获取输入流并直接从中创建文件.它没有File.Create像他那样使用(最初对我来说似乎有问题,但后来发现它可能只是一个VS bug ......).

/// <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();
}
Run Code Online (Sandbox Code Playgroud)

  • 关闭前应冲洗。尽管关闭也应该进行冲洗。 (2认同)

Geo*_*rge 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.
*/
Run Code Online (Sandbox Code Playgroud)

  • 逐个字节地复制流(使用ReadByte/WriteByte)将比逐个缓冲区复制慢得多(使用Read(byte [],int,int)/ Write(byte [],int,int)). (16认同)

小智 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);
     }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果输入流长度为1GB怎么办 - 这段代码会尝试分配1GB缓冲区:) (46认同)
  • Buthrakaur是对的 - 应该删除这个答案. (4认同)

naw*_*fal 6

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

using (var stream = new MemoryStream())
{
    input.CopyTo(stream);
    File.WriteAllBytes(file, stream.ToArray());
}
Run Code Online (Sandbox Code Playgroud)

用扩展方法包装它可以更好地命名:

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.
}
Run Code Online (Sandbox Code Playgroud)

  • 如果输入太大,则会出现内存不足异常。将内容从输入流复制到文件流的选项要好得多 (3认同)

小智 5

这是一个使用正确使用和实现 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());
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

……还有这个

    public static void WriteToFile(Stream 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());
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

关键是理解 using 的正确使用(应该在实现 idisposable 的对象的实例化上实现,如上所示),并很好地了解属性如何为流工作。位置实际上是流中的索引(从 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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