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

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

现有代码:

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

我也有编码类型,如果我将其存储到SQL服务器,我将需要它,正确的?

964046 次浏览

绝不能使用StreamReader作为二进制文件(如gifs或jpgs)。StreamReader用于文本数据。如果您将其用于任意二进制数据,您几乎会丢失当然数据。(如果您使用Encoding. GetEncode(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);}}

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

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

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

为什么不使用FileStream对象?

public void SaveStreamToFile(string fileFullPath, Stream stream){if (stream.Length == 0) return;
// Create a FileStream object to write a stream to a fileusing (FileStream fileStream = System.IO.File.Create(fileFullPath, (int)stream.Length)){// Fill the bytes[] array with the stream databyte[] bytesInStream = new byte[stream.Length];stream.Read(bytesInStream, 0, (int)bytesInStream.Length);
// Use FileStream object to write to the specified filefileStream.Write(bytesInStream, 0, bytesInStream.Length);}}

正如Tilendor在Jon Skeet的回答中强调的那样,从. NET 4开始,流就有了CopyTo方法。

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

如果您还没有开始,则必须调用Seek,否则您将无法复制整个流。

//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 encodingslike Unicode used in .Net where Char is one or more bytes (byte[n]). Also, theresulting translation from IEnumerable<byte> to IEnumerable<Char> can loose bytesor insert them (for example, "\n" vs. "\r\n") depending on the StreamReader instanceCurrentEncoding.*/
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);}}}
public void CopyStream(Stream stream, string destPath){using (var fileStream = new FileStream(destPath, FileMode.Create, FileAccess.Write)){stream.CopyTo(fileStream);}}
private void SaveFileStream(String path, Stream stream){var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);stream.CopyTo(fileStream);fileStream.Dispose();}

另一种选择是将流获取到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.}

我没有得到使用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)上使用过它。一个流,流之间没有复制,不需要编码,只需写入字节!如果你已经有一个可以直接用.ToArray()转换为bytes的流,就不需要用StreamReader把事情复杂化!

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

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

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

下面是一个使用i一次性的正确用法和实现的示例:

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

关键是理解使用的正确使用(应该在实现如上所示的i一次性的对象的实例化上实现),并对属性如何在流中工作有一个好的想法。位置实际上是流中的索引(从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 filedestinationFileStream.Write(networkBuffer, 0, networkBuffer.Length);}}}

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