与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
的内部工作期间才能复制/读取原始流。
答案 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
}
}