549

将一个流的内容复制到另一个流的最佳方法是什么?有没有标准的实用方法?

4

13 回答 13

726

从 .NET 4.5 开始,就有了Stream.CopyToAsync方法

input.CopyToAsync(output);

这将返回一个Task可以在完成后继续的,如下所示:

await input.CopyToAsync(output)

// Code from here on will be run in a continuation.

请注意,根据调用的位置,后面CopyToAsync的代码可能会或可能不会在调用它的同一线程上继续。

SynchronizationContext调用时捕获的await将确定继续将在哪个线程上执行。

此外,此调用(这是一个可能会更改的实现细节)仍然对读取和写入进行排序(它只是不会浪费线程在 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:为什么使用固定缓冲区大小而不是input.Length?因为该长度可能不可用!从文档

如果从 Stream 派生的类不支持查找,则对 Length、SetLength、Position 和 Seek 的调用将引发 NotSupportedException。

于 2008-10-23T15:19:32.957 回答
72

MemoryStream拥有.WriteTo(outstream);

并且 .NET 4.0 具有.CopyTo正常的流对象。

.NET 4.0:

instream.CopyTo(outstream);
于 2010-06-22T02:41:36.977 回答
33

我使用以下扩展方法。当一个流是 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);
    }
于 2009-08-10T03:54:43.533 回答
3

.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/>");
于 2012-07-27T08:42:23.447 回答
2

实际上,有一种不太严格的方式来进行流复制。但是请注意,这意味着您可以将整个文件存储在内存中。如果您正在处理数百兆字节或更多的文件,请不要尝试使用它,不要小心。

public static void CopySmallTextStream(Stream input, Stream output)
{
  using (StreamReader reader = new StreamReader(input))
  using (StreamWriter writer = new StreamWriter(output))
  {
    writer.Write(reader.ReadToEnd());
  }
}

注意:也可能存在一些关于二进制数据和字符编码的问题。

于 2009-08-10T03:38:01.507 回答
1

区分“CopyStream”实现的基本问题是:

  • 读取缓冲区的大小
  • 写入的大小
  • 我们可以使用多个线程(阅读时写作)。

这些问题的答案会导致 CopyStream 的完全不同的实现,并且取决于您拥有什么样的流以及您要优化什么。“最佳”实现甚至需要知道流正在读取和写入的特定硬件。

于 2008-10-23T15:29:53.700 回答
0

不幸的是,没有真正简单的解决方案。你可以尝试这样的事情:

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 类的不同实现可能会表现出不同的问题。从本地硬盘读取文件的流可能会阻塞,直到读取操作从磁盘读取足够的数据以填充缓冲区,并且只有在到达文件末尾时才返回较少的数据。另一方面,从网络读取的流可能会返回更少的数据,即使还有更多的数据需要接收。

在使用通用解决方案之前,请务必检查您正在使用的特定流类的文档。

于 2008-10-23T15:26:28.293 回答
0

可能有一种方法可以更有效地执行此操作,具体取决于您使用的流类型。如果可以将一个或两个流转换为 MemoryStream,则可以使用 GetBuffer 方法直接处理表示数据的字节数组。这使您可以使用 Array.CopyTo 之类的方法,这些方法可以抽象掉由 fryguybob 提出的所有问题。您可以相信 .NET 知道复制数据的最佳方式。

于 2008-10-23T17:24:29.383 回答
0

如果您想要一个程序将流复制到另一个 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
于 2009-03-22T16:44:40.327 回答
0

由于没有一个答案涵盖了从一个流复制到另一个流的异步方式,因此这是我在端口转发应用程序中成功使用的一种模式,用于将数据从一个网络流复制到另一个。它缺乏异常处理来强调模式。

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);
}
于 2011-11-07T21:05:55.557 回答
0

对于 .NET 3.5 及之前的尝试:

MemoryStream1.WriteTo(MemoryStream2);
于 2013-06-29T16:52:36.307 回答
0

简单安全 - 从原始来源制作新流:

    MemoryStream source = new MemoryStream(byteArray);
    MemoryStream copy = new MemoryStream(byteArray);
于 2018-04-30T12:47:48.820 回答
-3

以下代码解决了使用 CopyTo 将 Stream 复制到 MemoryStream 的问题

Stream stream = new MemoryStream();

//任何函数都需要输入流。在 mycase 中将 PDF 文件保存为流 document.Save(stream);

MemoryStream newMs = (MemoryStream)stream;

byte[] getByte = newMs.ToArray();

//注意 - 请将流放在 finally 块中,而不是在 using 块中,因为它会抛出错误 'Access denied as the stream is closed'

于 2020-08-06T02:52:11.443 回答