在C++中连续流式传输多个文件

5
我的问题类似于这个问题,但我没有找到任何关于C++的参考资料。
有一个大文件列表需要读取和处理。创建一个输入流的最佳方法是什么?该输入流将逐个从文件中获取数据,在前一个文件结束时自动打开下一个文件。此流将被传递给一个处理函数,该函数顺序读取可变大小的块,跨越文件边界。

好的,“Unixy”的方式是编写您的程序作为过滤器(即从stdin读取并写入stdout),然后使用现有的构建块,如cat input_file*.dat | myprogram。但是,如果没有更多细节(即文件是否都在一个可全局匹配的目录中,还是分散在各个地方,或者顺序需要不同),那么很难说更多了... - twalberg
你可以创建一个派生自 std::istream 的新类,其中包含一个 std::vectorstd::ifstream,它会在 EOF 或读取失败时自动切换到下一个。 - KABoissonneault
将它们收集到缓冲文件中,然后再读取它们?这是一个由两部分组成的操作。 - Charlie
2个回答

5
你需要做的是提供一个继承自std::basic_streambuf的类型。这个类型有许多神秘的virtual成员函数,其中与你相关的是showmanyc()underflow()uflow()xsgetn()。你需要重载它们,在溢出时自动打开列表中的下一个文件(如果有的话)。
以下是一个示例实现。我们充当一个std::filebuf,只需保留一个deque<string>的下一个需要读取的文件即可:
class multifilebuf : public std::filebuf
{
public:
    multifilebuf(std::initializer_list<std::string> filenames)
    : next_filenames(filenames.begin() + 1, filenames.end())
    {   
        open(*filenames.begin(), std::ios::in);
    }   

protected:
    std::streambuf::int_type underflow() override
    {   
        for (;;) {
            auto res = std::filebuf::underflow();
            if (res == traits_type::eof()) {
                // done with this file, move onto the next one
                if (next_filenames.empty()) {
                    // super done
                    return res;
                }
                else {
                    // onto the next file
                    close();
                    open(next_filenames.front(), std::ios::in);

                    next_filenames.pop_front();
                    continue;
                }
            }
            else {
                return res;
            }
        }
    }   

private:
    std::deque<std::string> next_filenames;
};

那样,您可以使所有内容对终端用户透明:
multifilebuf mfb{"file1", "file2", "file3"};

std::istream is(&mfb);
std::string word;
while (is >> word) {
    // transaparently read words from all the files
}

这些东西将在我接下来要问一个声称对C++了如指掌的人的问题中得到展示。不错的发现! - KABoissonneault
@KABoissonneault 甚至已经想出了如何制作一个工作示例。我猜这种情况并不那么糟糕,只需要使用 underflow() 即可。 - Barry

0

对于一个简单的解决方案,可以使用boost的join和istream迭代器来处理文件。我不知道当前C++库中是否存在类似的函数,但在TS Rangesv3中可能存在。

您也可以自己编写:编写join本身是完全可行的。

我会将其编写为“展平”输入迭代器——一个迭代器,它遍历一系列范围的内容。该迭代器将跟踪未来的范围和当前元素的迭代器。

这里是一个非常简单的zip迭代器,可以让您了解需要编写的代码量(zip迭代器是一个不同的概念,这只适用于for(:)循环)。

以下是使用C++14实现的草图:

template<class It>
struct range_t {
  It b{};
  It e{};
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
};

template<class It>
struct range_of_range_t {
  std::deque<range_t<It>> ranges;
  It cur;
  friend bool operator==(range_of_range_t const& lhs, range_of_range_t const& rhs) {
    return lhs.cur==rhs.cur;
  }
  friend bool operator!=(range_of_range_t const& lhs, range_of_range_t const& rhs) {
    return !(lhs==rhs);
  }
  void operator++(){
    ++cur;
    if (ranges.front().end() == cur) {
      next_range();
    }
  }
  void next_range() {
    while(ranges.size() > 1) {
      ranges.pop_front();
      if (ranges.front().empty()) continue;
      cur = ranges.front().begin();
      break;
    }
  }
  decltype(auto) operator*() const {
    return *cur;
  }
  range_of_range_t( std::deque<range_t<It>> in ):
    ranges(std::move(in)),
    cur{}
  {
    // easy way to find the starting cur:
    ranges.push_front({});
    next_range();
  }
};

迭代器需要改进,它应该支持所有的迭代器公理。而正确获取结束迭代器也需要一些工作。

这不是一个流,而是一个迭代器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接