如何仅在目标使用时将流复制到另一个流

时间:2015-10-13 22:46:00

标签: c# .net stream

How do I copy the contents of one stream to another?类似

但我对view1的理解意味着它将从头到尾读取整个sourceStream.CopyTo(destStream)(块或其他)以便复制它,然后消费者返回并读取流再次(它的副本),导致 O(2n)而不是 O(n),对吧?如果sourceStream是临时副本(即destStream),那么我最终还会将整个源流加载到每个副本的内存中。

有没有办法做到这一点,只有在MemoryStream被消费/阅读时才被复制?

具体来说,在.NET C#中,我需要复制一个输入流并将其写入多个“目标”(通过各种帮助程序库,其中一些处理它们给出的流)。输入可能非常大,通常实际上是destStream,所以当我可以将它倒回并从磁盘缓冲它时,我宁愿不将整个文件加载到内存中。

示例场景:

FileStream

如果我在调用void WriteToMany(Stream sourceStream, IEnumerable<ICanPutStream> destinations) { foreach(var endpoint in destinations) { // <-- I need to make a copy of `stream` here because... endpoint.PutStream(sourceStream); // ...some endpoints automatically dispose the stream } } 之前复制,它将读取源流。我可以接受它,但是如果我将它复制到PutStream它也会将它加载到内存中为每个端点(添加了一些奇怪的尝试处理可能/不可能的东西已经处理好了)。理想情况下,只有在MemoryStream的内部工作期间才能复制/读取原始流。

2 个答案:

答案 0 :(得分:2)

除非您可以回到流的开头,否则必须将整个流复制到内存以拥有多个使用者。否则,流数据仅供第一个消费者使用。

如果您有一个可搜索的流(如FileStream),并且您希望将其传递给多个消费者而不进行处理,则可以实现Stream代理,该代理将所有成员委派给基础流,除了为Dispose。它看起来像是:

class StreamProxy : Stream
{
    private readonly Stream _stream;

    public StreamProxy(Stream stream)
    {
        if (stream == null) throw new ArgumentNullException(nameof(stream));
        _stream = stream;
    }

    protected override void Dispose(bool disposing)
    {
        //don't dispose inner stream
    }

    public override void Flush()
    {
        _stream.Flush();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return _stream.Seek(offset, origin);
    }

    public override void SetLength(long value)
    {
        _stream.SetLength(value);
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return _stream.Read(buffer, offset, count);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        _stream.Write(buffer, offset, count);
    }

    public override bool CanRead
    {
        get { return _stream.CanRead; }
    }

    public override bool CanSeek
    {
        get { return _stream.CanSeek; }
    }

    public override bool CanWrite
    {
        get { return _stream.CanWrite; }
    }

    public override long Length
    {
        get { return _stream.Length; }
    }

    public override long Position
    {
        get { return _stream.Position; }
        set { _stream.Position = value; }
    }
}

这样,每个消费者都可以处置他们的流的“副本”(StreamProxy的实例),而不会丢弃底层流。消费者完成后,寻找基础流回到开头并将代理传递给另一个消费者。

关于你的问题:有没有办法做到这一点,只有在消耗/读取destStream时才复制它?你可以扩充上面的StreamProxy类,以便它跟踪它在内流中的位置。然后,对于每次读取操作,StreamProxy将负责寻找内部流到适当的位置并读取下一个块。这样,每个消费者都会收到他们自己的StreamProxy实例,并且可以从独立位置的内部流中读取。

除非您的消费者并行运行,否则我认为这种方法优于最初提出的StreamProxy没有任何优势。如果是,则还需要StreamProxy中的同步机制,以便读取不重叠,因为内部流只能一次位于一个位置。这有效地序列化了消费者(这是从单个Stream开始并且不将其内容复制到内存的固有限制),并且使得该方法总体上效率较低(除非在读取性能之间存在巨大差异)内流和消费者的写作表现。)

您的新阅读方法可能如下所示:

public override int Read(byte[] buffer, int offset, int count)
{
    lock(_stream)
    {
        //position the inner stream to end of last read (another consumer may have moved it)
        _stream.Seek(Position, SeekOrigin.Begin);

        //read the bytes, up to count
        var count = _stream.Read(buffer, offset, count);

        //update the next read position
        Position += count;

        return count;
    }
}

public override long Position{get;set;}

答案 1 :(得分:0)

Stream.CopyTo内部有一个缓冲区(81920字节,如果你没有在重载方法上设置它)。它的实现非常简单,所以你可以改变它并像这样使用它:

void ConsumeStream(Stream source, Stream destination, int bufferSize)
{
    byte[] buffer = new byte[bufferSize];
    int count;
    while ((count = source.Read(buffer, 0, buffer.Length)) != 0)
    {
        destination.Write(buffer, 0, count);
        //Other stuff
    }
}