【问题标题】:Stitching together multiple streams in one Stream class在一个 Stream 类中拼接多个流
【发布时间】:2011-04-27 04:22:51
【问题描述】:

我想创建一个在其构造函数中采用IEnumerable<Stream> 的类(我们称之为HugeStream 类)。这个 HugeStream 应该实现 Stream 抽象类。

基本上,我有 1 到多个来自数据库的 UTF8 流,当它们放在一起时,会形成一个巨大的 XML 文档。 HugeStream 需要有文件支持,以便我可以随时返回到整个拼接在一起的流的位置 0。

有人知道如何快速实现这个吗?

我在this page 看到了类似的东西,但它似乎不是处理大量大型流的最佳选择。效率是关键。

顺便说一句,我在可视化 Streams 时遇到了麻烦,现在我有点困惑,因为我需要实现自己的 Stream。如果有任何人都知道的关于实现 Stream 类的好教程,请告诉我;我还没有找到任何好的文章浏览。我刚刚看到很多关于使用已经存在的 FileStreams 和 MemoryStreams 的文章。我是一个非常直观的学习者,由于某种原因找不到任何有用的东西来研究这个概念。

谢谢,

马特

【问题讨论】:

  • 你的问题不清楚。问题是什么?您可以简单地将流一个接一个地写入一个大文件。浏览流,可以使用Encoding.UTF8.GetString(byteArray),还需要吗?

标签: c# stream iostream


【解决方案1】:

如果你只是从 HugeStream 中顺序读取数据,那么它只需要读取每个子流(并将其附加到本地文件中,并将读取的数据返回给调用者)直到子流耗尽,然后继续下一个子流。如果使用 Seek 操作在数据中“向后”跳转,则必须从本地缓存文件开始读取;当你到达缓存文件的末尾时,你必须从你离开的地方继续读取当前的子流。

到目前为止,这一切都非常简单地实现 - 您只需将 Read 调用间接到适当的流,并在每个流用完数据时切换流。

引用文章的低效之处在于,它每次在您阅读时都会遍历所有流,以找出从哪里继续阅读。为了改进这一点,您需要仅在需要时打开子流,并跟踪当前打开的流,以便您可以继续从当前流中读取更多数据,直到它耗尽。然后打开下一个流作为您的“当前”流并继续。这很简单,因为你有一个线性的流序列,所以你只需一个接一个地遍历它们。即类似:

int currentStreamIndex = 0;
Stream currentStream = childStreams[currentStreamIndex++];

...

public override int Read(byte[] buffer, int offset, int count)
{
    while (count > 0)
    {
        // Read what we can from the current stream
        int numBytesRead = currentSteam.Read(buffer, offset, count);
        count -= numBytesRead;
        offset += numBytesRead;

        // If we haven't satisfied the read request, we have exhausted the child stream.
        // Move on to the next stream and loop around to read more data.
        if (count > 0)
        {
            // If we run out of child streams to read from, we're at the end of the HugeStream, and there is no more data to read
            if (currentStreamIndex >= numberOfChildStreams)
                break;

            // Otherwise, close the current child-stream and open the next one
            currentStream.Close();
            currentStream = childStreams[currentStreamIndex++];
        }
    }

   // Here, you'd write the data you've just read (into buffer) to your local cache stream
}

为了允许向后搜索,您只需要引入一个新的本地文件流,您在阅读时将所有数据复制到该文件流中(请参阅上面我的伪代码中的注释)。您需要引入一个状态,以便您知道您正在从缓存文件而不是当前子流中读取,然后直接访问缓存(寻找等很容易,因为缓存代表从 HugeStream 读取的数据的整个历史,因此 HugeStream 和 Cache 之间的查找偏移量是相同的 - 您只需重定向任何 Read 调用即可从缓存流中获取数据)

如果读取或回溯到缓存流的末尾,则需要从当前子流中继续读取数据。只需回到上面的逻辑并继续将数据附加到您的缓存流。

如果您希望能够在 HugeStream 中支持完全随机访问,则需要支持寻找“转发”(超出缓存流的当前端)。如果您事先不知道子流的长度,您别无选择,只能继续将数据读取到缓存中,直到达到查找偏移量。如果您知道所有流的大小,那么您可以直接更有效地寻找正确的位置,但是您必须设计一种有效的方法来将您读取的数据存储到缓存文件并记录缓存的哪些部分文件包含有效数据,但实际上尚未从数据库中读取 - 这有点高级。

我希望这对您有意义,并让您更好地了解如何继续......

(您不需要实现比 Read 和 Seek 接口更多的东西就可以让它工作)。

【讨论】:

  • 杰森,感谢您的深入解释。今天晚些时候,我将实现我的 Stream,看看我是否可以让它按预期工作。一旦我的声望达到 15,我会投票赞成你的答案。 :)
  • 对了,你在伪代码中写的OpenStream方法是什么?不能将 currentStream 设置为 childStreams[currentStreamIndex++],还是需要额外处理才能“打开”流?
  • OpenStream 只是伪代码,用于向您显示在哪里打开子流。即假设您已经创建了流 (childstream[i] = new ???Stream();) 那么您将使用 childStream.Open() 来实际打开流,然后使用 childStream.Read() 和 childStream.Close()你已经完成了。
  • C# 流没有 Open() 方法。调用 Read 时是否“打开”?
  • 糟糕 - 大脑不正常!是的,当您创建一个新的 C# 流时,您不需要打开它,只需从它开始读取即可。 (我在考虑像 File.Open() 这样的调用,它打开一个文件并为其返回一个 FileStream,我在其他语言中花了很多年,你必须显式地打开流)。你是对的 - 你不需要 OpenStream() 位!许多道歉。我会更正伪代码...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-02
  • 2021-07-29
  • 2016-08-19
  • 1970-01-01
相关资源
最近更新 更多