我有一个用流初始化的StreamReader对象,现在我想将这个流保存到磁盘(流可以是.gif、.jpg或.pdf)。
现有代码:
StreamReader sr = new StreamReader(myOtherObject.InputStream);
我需要将其保存到磁盘(我有文件名)。将来我可能希望将其存储到SQL Server。
我还有编码类型,如果我将其存储到SQL Server,我将需要它,对吗?
我有一个用流初始化的StreamReader对象,现在我想将这个流保存到磁盘(流可以是.gif、.jpg或.pdf)。
现有代码:
StreamReader sr = new StreamReader(myOtherObject.InputStream);
我需要将其保存到磁盘(我有文件名)。将来我可能希望将其存储到SQL Server。
我还有编码类型,如果我将其存储到SQL Server,我将需要它,对吗?
当前回答
我没有使用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();
}
}
虽然我承认我只在小文件(小于1MB)中使用过,但这段代码的工作原理与我用.jpg文件测试的一样。一个流,无需在流之间复制,无需编码,只需写入字节!如果您已经有了可以直接使用.ToArray()转换为字节的流,那么无需使用StreamReader使事情变得过于复杂!
我认为这样做的唯一潜在缺点是,如果有一个大文件,将其作为流,并使用.CopyTo()或等效方法,FileStream可以将其流化,而不是使用字节数组并逐个读取字节。结果,这样做可能会慢一些。但是它不应该阻塞,因为FileStream的.Write()方法处理字节的写入,并且它一次只写一个字节,所以它不会阻塞内存,除非您必须有足够的内存将流作为byte[]对象保存。在我使用这个的情况下,获得OracleBlob时,我必须使用一个字节[],它足够小,而且,无论如何,我没有可用的流,所以我只将字节发送到我的函数,如上所述。
另一种选择,使用流,是将它与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();
}
其他回答
下面是一个使用idispable的正确用法和实现的示例:
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());
}
}
}
关键是理解using的正确用法(应该在实现idisposable的对象的实例化中实现,如上图所示),并对流的财产是如何工作的有一个好主意。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与缓冲区大小和原始缓冲区大小进行比较,如果它大于原始字节数组的大小,则结束运行。
我没有使用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();
}
}
虽然我承认我只在小文件(小于1MB)中使用过,但这段代码的工作原理与我用.jpg文件测试的一样。一个流,无需在流之间复制,无需编码,只需写入字节!如果您已经有了可以直接使用.ToArray()转换为字节的流,那么无需使用StreamReader使事情变得过于复杂!
我认为这样做的唯一潜在缺点是,如果有一个大文件,将其作为流,并使用.CopyTo()或等效方法,FileStream可以将其流化,而不是使用字节数组并逐个读取字节。结果,这样做可能会慢一些。但是它不应该阻塞,因为FileStream的.Write()方法处理字节的写入,并且它一次只写一个字节,所以它不会阻塞内存,除非您必须有足够的内存将流作为byte[]对象保存。在我使用这个的情况下,获得OracleBlob时,我必须使用一个字节[],它足够小,而且,无论如何,我没有可用的流,所以我只将字节发送到我的函数,如上所述。
另一种选择,使用流,是将它与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();
}
正如JonSkeet的回答中Tilendor所强调的,自.NET4以来,流具有CopyTo方法。
var fileStream = File.Create("C:\\Path\\To\\File");
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
fileStream.Close();
或者使用以下语法:
using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
}
如果尚未开始,则必须调用Seek,否则无法复制整个流。
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();
}