使用 istreambuf_iterator 与 copy_n 读取文件

Reading files with istreambuf_iterator vs copy_n

提问人:SupAl 提问时间:9/6/2023 最后编辑:SupAl 更新时间:9/7/2023 访问量:51

问:

我想解析不同长度的文件中不同类型的块,所以我创建了一个函数,通过传入 ifstream 来读出块,如下所示:

void parse_next(std::ifstream& input_file, std::vector<uint8_t>& data, size_t count)
{
    std::copy_n(
        std::istreambuf_iterator<char>(input_file),
        count,
        std::back_inserter(data)
    );
}

我希望文件位置增加计数,即

// some init code
size_t const pos_before{input_file.tellg()};
parse_next(input_file, data, count);
size_t const pos_after{input_file.tellg()};

// this assumption is _not_ correct! 
assert(count == (pos_after - pos_before)); 

// but this is!
assert((count - 1) == (pos_after - pos_before));

但是,使用 with count 而不是 count 会给出正确的计数。input_file.read()std::copy_n

那么这是怎么回事呢?我在istreambuf_iterator的文档中看不到提到这一点的任何地方。 还是惹我了?std::copy_n

请注意,在上面的示例中,我们可以假设还有很多数据需要读取,所以这不是因为文件是空的。 此外,该文件以二进制形式打开。

C++ STL IFStlet

评论

0赞 Retired Ninja 9/6/2023
这两个断言都不起作用,因为它们缺少 .什么是之前和之后?)data.size()
0赞 SupAl 9/6/2023
data.size()递增 (with and )。修复了缺少的断言 ')'。countstd::copy_n().read()
0赞 Retired Ninja 9/6/2023
parse_next需要通过引用接受 vector 参数。尝试此操作时引起了一些小混淆。godbolt.org/z/jzb3co6Wx有趣的是,这个职位比你预期的要少一个,而且读得越多,就会产生重复。我不知道你问题的答案,但它确实让我很高兴我避免写这样的代码。
0赞 SupAl 9/7/2023
@RetiredNinja,忘记了对数据的引用 - 它在我更详细的代码中。在这里,我只是提取了要点。你会怎么写这个?
0赞 Retired Ninja 9/7/2023
我会用.如果我读取的是固定数量的项目,我会调整向量的大小并使用一次读取,如果我不知道有多少个项目,我会一次读取一个并push_back直到读取失败。read

答:

2赞 John Zwinck 9/7/2023 #1

您正在使用 .它是一个仅输入迭代器。想象一下,你有一个 5 个字节的文件,你读到:istreambuf_iteratorcount=2

  1. 它调用 sgetc 来读取第一个字节。这不会推进流位置。
  2. 由于 ,需要再一个字节。因此,它增加了流位置。count=2copy_n
  3. 它使用 读取第二个字节。sgetc
  4. 由于 ,因此不再需要字节。 返回。count=2copy_n

请注意,只有步骤 2 会递增流位置,并且只需要在读取两个字符时调用一次。

是的,这很奇怪。但大多数人只会使用 .我几乎从未见过人们在生产代码中使用......尤其是因为它对于您的用例类型来说效率低下。input_file.read()istreambuf_iterator

我们可以说,嘿,让我们在返回之前更改以递增迭代器。这将解决这个 0.1% 的用例,但代价是减慢其他用例的速度。copy_n

评论

0赞 SupAl 9/7/2023
所以这就是关键 - 在引擎盖下使用。我真的以为这会超载到或'''memmove'''。但是,当然,对于back_inserter,我们可能需要在此过程中进行一些动态分配。因此,获得最佳性能的标准方法是执行 resize(),然后读取 std::vector(假设我仍然想使用 std::vector,如果我想保留动态分配,这可能是我的最佳选择),或者你有更好的方法吗?sgetcstd::copy_nmemcpy
0赞 Retired Ninja 9/7/2023
这样解释是完全有道理的,这对我来说是违反直觉的,因为我希望实际读取会推进文件指针而不是迭代器。当我看到人们使用这种模式时,通常是读取整个文件,所以在这种情况下,无论哪种方式似乎都无关紧要。谢谢你教我一些新东西。:)