如何最好地将一个流的内容复制到另一个流中?是否有标准的实用方法可以做到这一点?
.NET 4.5以后,有一个名为Stream.CopyToAsync
的方法。
input.CopyToAsync(output);
这将返回一个Task
,当其完成后可以继续执行下去,例如:
await input.CopyToAsync(output)
// Code from here on will be run in a continuation.
请注意,根据调用 CopyToAsync
的位置不同,其后的代码可能会继续在调用它的线程上执行,也可能不会。
调用 await
时捕获的 SynchronizationContext
将确定后续操作将在哪个线程上执行。
另外,该调用(这是一个可能会更改的实现细节)仍然按顺序读取和写入(只是不浪费线程阻塞 I/O 完成)。
从 .NET 4.0 开始,有 Stream.CopyTo
方法
input.CopyTo(output);
对于 .NET 3.5 及之前版本
框架中没有任何内置功能来协助这个操作;您需要手动复制内容,像这样:
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[32768];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
output.Write (buffer, 0, read);
}
}
注意1:这种方法可以让你报告进度(已读取x个字节...)
注意2:为什么要使用固定的缓冲区大小而不是 ?因为该长度可能无法获得!文档如下:
如果从Stream派生的类不支持寻址,则调用Length、SetLength、Position和Seek会抛出NotSupportedException异常。
81920
字节,而不是 32768
。 - Alex ZhukovskiyMemoryStream
有 .WriteTo(outstream);
而在 .NET 4.0 中,普通流对象有 .CopyTo
方法。
.NET 4.0:
instream.CopyTo(outstream);
我使用以下扩展方法。它们针对一个流为MemoryStream的情况进行了优化。
public static void CopyTo(this Stream src, Stream dest)
{
int size = (src.CanSeek) ? Math.Min((int)(src.Length - src.Position), 0x2000) : 0x2000;
byte[] buffer = new byte[size];
int n;
do
{
n = src.Read(buffer, 0, buffer.Length);
dest.Write(buffer, 0, n);
} while (n != 0);
}
public static void CopyTo(this MemoryStream src, Stream dest)
{
dest.Write(src.GetBuffer(), (int)src.Position, (int)(src.Length - src.Position));
}
public static void CopyTo(this Stream src, MemoryStream dest)
{
if (src.CanSeek)
{
int pos = (int)dest.Position;
int length = (int)(src.Length - src.Position) + pos;
dest.SetLength(length);
while(pos < length)
pos += src.Read(dest.GetBuffer(), pos, length - pos);
}
else
src.CopyTo((Stream)dest);
}
.NET Framework 4引入了System.IO命名空间中Stream类的新“CopyTo”方法。使用此方法,我们可以将一个流复制到不同流类的另一个流。
以下是示例:
FileStream objFileStream = File.Open(Server.MapPath("TextFile.txt"), FileMode.Open);
Response.Write(string.Format("FileStream Content length: {0}", objFileStream.Length.ToString()));
MemoryStream objMemoryStream = new MemoryStream();
// Copy File Stream to Memory Stream using CopyTo method
objFileStream.CopyTo(objMemoryStream);
Response.Write("<br/><br/>");
Response.Write(string.Format("MemoryStream Content length: {0}", objMemoryStream.Length.ToString()));
Response.Write("<br/><br/>");
实际上,有一种更轻量级的方式进行流复制。但要注意,这意味着您可以将整个文件存储在内存中。如果您处理的文件超过数百兆字节,请谨慎使用。
public static void CopySmallTextStream(Stream input, Stream output)
{
using (StreamReader reader = new StreamReader(input))
using (StreamWriter writer = new StreamWriter(output))
{
writer.Write(reader.ReadToEnd());
}
}
注意:还可能存在有关二进制数据和字符编码的一些问题。
reader.ReadToEnd()
会将所有内容放入内存中。 - Bizhan区分“CopyStream”实现的基本问题包括:
这些问题的答案导致了CopyStream的大不相同的实现,并且取决于您拥有的流的类型以及您要优化的内容。 “最佳”实现甚至需要知道流正在读取和写入的具体硬件。
对于 .NET 3.5 及之前版本,请尝试:
MemoryStream1.WriteTo(MemoryStream2);
如果你想要一个将流复制到另一个流的程序,Nick 发布的那个不错,但它缺少位置重置,应该是:
public static void CopyStream(Stream input, Stream output)
{
byte[] buffer = new byte[32768];
long TempPos = input.Position;
while (true)
{
int read = input.Read (buffer, 0, buffer.Length);
if (read <= 0)
return;
output.Write (buffer, 0, read);
}
input.Position = TempPos;// or you make Position = 0 to set it at the start
}
但如果在运行时不使用过程,则应该使用内存流
Stream output = new MemoryStream();
byte[] buffer = new byte[32768]; // or you specify the size you want of your buffer
long TempPos = input.Position;
while (true)
{
int read = input.Read (buffer, 0, buffer.Length);
if (read <= 0)
return;
output.Write (buffer, 0, read);
}
input.Position = TempPos;// or you make Position = 0 to set it at the start
很遗憾,没有真正简单的解决方案。你可以尝试像这样的东西:
Stream s1, s2;
byte[] buffer = new byte[4096];
int bytesRead = 0;
while (bytesRead = s1.Read(buffer, 0, buffer.Length) > 0) s2.Write(buffer, 0, bytesRead);
s1.Close(); s2.Close();
但是问题在于,如果没有可读取的内容,Stream类的不同实现可能会表现出不同的行为。从本地硬盘读取文件的流可能会阻塞,直到读取操作从磁盘中读取足够的数据来填充缓冲区,并且只有在到达文件结尾时才返回较少的数据。另一方面,从网络读取的流可能会返回较少的数据,即使还有更多的数据需要接收。
在使用通用解决方案之前,请始终检查您正在使用的特定流类的文档。
由于没有任何答案涵盖从一个流异步复制到另一个流的方式,因此在这里介绍一种模式,我已经成功地在端口转发应用程序中使用它来将数据从一个网络流复制到另一个网络流。为了强调模式,它缺少异常处理。
const int BUFFER_SIZE = 4096;
static byte[] bufferForRead = new byte[BUFFER_SIZE];
static byte[] bufferForWrite = new byte[BUFFER_SIZE];
static Stream sourceStream = new MemoryStream();
static Stream destinationStream = new MemoryStream();
static void Main(string[] args)
{
// Initial read from source stream
sourceStream.BeginRead(bufferForRead, 0, BUFFER_SIZE, BeginReadCallback, null);
}
private static void BeginReadCallback(IAsyncResult asyncRes)
{
// Finish reading from source stream
int bytesRead = sourceStream.EndRead(asyncRes);
// Make a copy of the buffer as we'll start another read immediately
Array.Copy(bufferForRead, 0, bufferForWrite, 0, bytesRead);
// Write copied buffer to destination stream
destinationStream.BeginWrite(bufferForWrite, 0, bytesRead, BeginWriteCallback, null);
// Start the next read (looks like async recursion I guess)
sourceStream.BeginRead(bufferForRead, 0, BUFFER_SIZE, BeginReadCallback, null);
}
private static void BeginWriteCallback(IAsyncResult asyncRes)
{
// Finish writing to destination stream
destinationStream.EndWrite(asyncRes);
}