【问题标题】:stream several files continuously in C++在 C++ 中连续流式传输多个文件
【发布时间】:2023-03-28 03:32:01
【问题描述】:

我的问题类似于this,但我没有找到任何针对此问题的 C++ 参考。

有一个要读取和处理的大文件列表。创建一个输入流的最佳方法是从文件中逐个获取数据,在前一个文件结束时自动打开下一个文件?该流将被提供给一个处理函数,该函数跨文件边界顺序读取可变大小的块。

【问题讨论】:

  • 嗯,“Unixy”方式是将您的程序编写为过滤器(即,它从标准输入读取并写入标准输出),然后使用现有的构建块,如 cat input_file*.dat | myprogram。但是如果没有更多细节(即文件是否都在一个目录中,并且名称是全局的,或者它们是否分布在不同的地方,或者顺序需要不同),很难说比这更多......
  • 您可以创建一个派生自 std::istream 的新类,其中包含 std::vectorstd::ifstream,在 EOF 或读取失败时自动切换到下一个
  • 将它们收集到缓冲区文件中,然后再读取它们?所以一个两部分操作

标签: c++ c++11


【解决方案1】:

您需要提供一个继承自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 Even 继续研究如何制作一个工作示例。我想这个案子还不错,只需要underflow()
【解决方案2】:

对于一个简单的解决方案,使用boost's join 与文件的 istream 迭代器范围。我不知道当前 C++ 库中有类似的函数,但可能存在于 TS Rangesv3 中。

你也可以自己写:自己写加入是完全可能的。

我会把它写成一个“扁平化”的仅输入迭代器——一个范围范围内的迭代器,它依次迭代每个范围的内容。迭代器将跟踪范围的未来范围,以及当前元素的迭代器。

Here 是一个非常简单的 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();
  }
};

迭代器需要工作,因为它应该支持所有的迭代器公理。让结束迭代器正确是一项工作。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 2011-12-30
    • 1970-01-01
    相关资源
    最近更新 更多