我有一个StreamReader
用流初始化的对象,现在我想将此流保存到磁盘(流可能是.gif
or.jpg
或.pdf
)。
现有代码:
StreamReader sr = new StreamReader(myOtherObject.InputStream);
- 我需要将其保存到磁盘(我有文件名)。
- 将来我可能希望将其存储到 SQL Server。
我也有编码类型,如果我将它存储到 SQL Server,我将需要它,对吗?
正如 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();
或使用以下using
语法:
using (var fileStream = File.Create("C:\\Path\\To\\File"))
{
myOtherObject.InputStream.Seek(0, SeekOrigin.Begin);
myOtherObject.InputStream.CopyTo(fileStream);
}
不得用于二进制文件(如 gif 或 jpg)。用于文本数据。如果您将其用于任意二进制数据,您几乎肯定会丢失数据。(如果你使用 Encoding.GetEncoding(28591) 你可能会没事,但有什么意义呢?)StreamReader
StreamReader
为什么你需要使用 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 中引入的,目的基本相同。
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();
}
我没有使用 得到所有答案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();
}
下面是一个正确使用和实现 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(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 的对象的实例化上实现),并且对属性如何为流工作有一个好主意。位置实际上是流中的索引(从 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 与缓冲区的大小和原始缓冲区的大小进行比较,如果它大于原始大小字节数组,结束运行。
为什么不使用 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);
}
}
//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.
*/
另一种选择是将流获取到 abyte[]
并使用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.
}
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);
}
}
}