如何在.NET中拆分(复制)流?

有谁知道我在哪里可以找到Stream splitter实现?

我正在寻找一个Stream,并获得两个独立的流,可以独立读取和关闭,而不会相互影响。 这些流应该返回与原始流相同的二进制数据。 无需实施职位或寻求等……仅转发。

我更喜欢它不只是将整个流复制到内存中并多次提供服务,这对于实现自己来说相当简单。

那里有什么可以做到的吗?

没有开箱即用。

您需要以FIFO方式缓冲原始流中的数据,仅丢弃所有“读取器”流已读取的数据。

我用的是:

  • 一个“管理”对象,其中包含某种byte []队列,用于保存要缓冲的块,并在需要时从源流中读取其他数据
  • 一些“读者”实例知道他们正在读取的位置和缓冲区,并从“管理”请求下一个块,并在它们不再使用块时通知它,以便可以从队列中删除它

这可能是棘手的,没有冒着将所有内容缓冲在内存中的风险(如果流分别处于BOF和EOF)。

我想知道将流写入磁盘,复制它并从磁盘读取两个流是否更容易,并在Close()内置自删除(即在FileStream周围编写自己的Stream包装器)。

如果不重复至少部分的源流,你就不能真正做到这一点 – 主要是因为如果听起来不像你可以控制它们被消耗的速度(multithreading?)。 你可以做一些聪明的事情,一个人阅读另一个人的读取(从而只在那一点制作副本),但这种复杂情况听起来似乎不值得麻烦。

我不认为你能找到一个通用的实现来做到这一点。 Stream是相当抽象的,您不知道字节的来源。 例如,你不知道它是否会支持寻求; 而且你不知道运营的相对成本。 (Stream可能是从远程服务器读取数据的抽象,甚至是备份磁带!)。

如果您能够拥有一个MemoryStream并存储一次内容,则可以使用相同的缓冲区创建两个单独的流; 它们将表现为独立的Streams但只使用一次内存。

否则,我认为你最好通过创建一个包装类来存储从一个流读取的字节,直到它们也被第二个流读取。 这将为您提供所需的仅向前行为 – 但在最坏的情况下,如果在第一个Stream已完成读取所有内容之前未读取第二个流,则可能存在将所有字节存储在内存中的风险。

我在github和NuGet上提供了一个SplitStream。

它是这样的。

 using (var inputSplitStream = new ReadableSplitStream(inputSourceStream)) using (var inputFileStream = inputSplitStream.GetForwardReadOnlyStream()) using (var outputFileStream = File.OpenWrite("MyFileOnAnyFilestore.bin")) using (var inputSha1Stream = inputSplitStream.GetForwardReadOnlyStream()) using (var outputSha1Stream = SHA1.Create()) { inputSplitStream.StartReadAhead(); Parallel.Invoke( () => { var bytes = outputSha1Stream.ComputeHash(inputSha1Stream); var checksumSha1 = string.Join("", bytes.Select(x => x.ToString("x"))); }, () => { inputFileStream.CopyTo(outputFileStream); }, ); } 

我没有在非常大的流上测试它,但试一试。

github: https : //github.com/microknights/SplitStream

随着async / await的引入,只要您的一个读取任务都是异步的,您应该能够仅使用一个OS线程处理相同的数据两次。

我想你想要的是你到目前为止看到的数据块的链接列表。 然后,您可以拥有多个自定义Stream实例,这些实例将指针保存到此列表中。 当块从列表的末尾掉落时,它们将被垃圾收集。 立即重用内存需要一些其他类型的循环列表和引用计数。 可行,但更复杂。

当您的自定义Stream可以从缓存中应答ReadAsync调用时,复制数据,将指针向下推进列表并返回。

当您的Stream已赶上缓存列表的末尾时,您希望在不等待它的情况下向基础流发出单个ReadAsync,并使用数据块缓存返回的Task。 因此,如果任何其他Stream阅读器也会在此读取完成之前赶上并尝试阅读更多内容,则可以返回相同的Task对象。

这样,两个读者都会将它们的等待延续挂钩到同一个ReadAsync调用的结果。 当单个读取返回时,两个读取任务将顺序执行其进程的下一步骤。