如何检测二进制文件是否被完全消费?

How Can I Detect That a Binary File Has Been Completely Consumed?

如果我这样做:

ofstream ouput("foo.txt");

output << 13;
output.close();

ifstream input("foo.txt");
int dummy;

input >> dummy;

cout << input.good() << endl;

我会得到结果:“0”

但是如果我这样做:

ofstream ouput("foo.txt", ios_base::binary);
auto dummy = 13;

output.write(reinterpret_cast<const char*>(&dummy), sizeof(dummy));
output.close();

ifstream input("foo.txt", ios_base::binary);

input.read(reinterpret_cast<char*>(&dummy), sizeof(dummy));
cout << input.good() << endl;

我会得到结果:“1”

这让我很沮丧。我是否必须诉诸于检查 ifstream 的缓冲区以确定它是否已被完全消耗?

你们在做完全不同的事情。

operator>> 是贪心的,会尽可能多地读入 dummy。碰巧在这样做时,它遇到了文件末尾。这设置了 input.eof(),并且流不再是 good()。由于在结束前确实找到了一些数字,因此操作仍然成功。

在第二次读取中,您请求特定数量的字节(最有可能是 4 个)并且读取成功。所以流还是good()

流接口不预测任何未来的结果I/O,因为在一般情况下它无法知道。如果您使用 cin 而不是 input,如果用户继续输入,现在可能会有更多内容可供阅读。

具体来说,eof() 状态不会出现,直到有人试图读取过去的文件末尾。

对于文本流,由于您只写入了整数值,甚至没有 space 而不是行尾,因此在读取时,库必须尝试读取通过 [=10= 的一个字符] 和 3 并到达文件末尾。所以 good bit 是 false 而 eof 是 true.

对于二进制流,假设 int 为 32 位大,您写入了 4 个字节 (sizeof(int)),并且您读取了 4 个字节。美好的。没有问题仍然发生,好的部分是 true 和 eof false。只有下一次读取才会到达文件末尾。

但要小心。在文本示例中,如果您在编辑器中打开文本文件并简单地保存它而不做任何更改,编辑器很可能会自动添加行尾。在那种情况下,读取将在行尾停止,对于二进制情况,好位将为真而 eof 为假。同样是你写 output << 13 << std::endl;

这意味着您绝不能假定读取的内容不是文件的最后一个元素,当它为真且 eof 为假时,因为即使没有返回任何内容,文件结尾也可能仅在下一次读取时命中那么

TL/DR:知道文件中没有任何内容的唯一万无一失的方法是当您不再能够从中读取任何内容时。

关于

How Can I Detect That a Binary File Has Been Completely Consumed?

一种效率稍低但易于理解的方法是测量文件的大小:

ifstream input("foo.txt", ios_base::binary);
input.seekg(0, ios_base::end); // go to end of the file
auto filesize = input.tellg(); // current position is the size of the file
input.seekg(0, ios_base::beg); // go back to the beginning of the file

然后随时检查当前位置:

if (input.tellg() == filesize)
    cout << "The file was consumed";
else
    cout << "Some stuff left in the file";

这种方式有一些缺点:

  • 效率不高 - 在文件中来回移动
  • 不适用于特殊文件(例如管道)
  • 如果文件被更改则不起作用(例如,您以读写模式打开文件)
  • 仅适用于二进制文件(看起来是你的情况,没问题),不适用于文本文件

所以最好使用人们的常规方式,即尝试阅读并在失败时保释:

if (input.read(reinterpret_cast<char*>(&dummy), sizeof(dummy)))
    cout << "I have read the stuff, will work on it now";
else
    cout << "No stuff in file";

或者(循环)

while (input.read(reinterpret_cast<char*>(&dummy), sizeof(dummy)))
{
    cout << "Working on your stuff now...";
}

您不需要求助于检查缓冲区。您可以确定整个文件是否已被消耗:cout << (input.peek() != char_traits<char>::eof()) << endl 这使用:peek,其中:

Reads the next character from the input stream without extracting it

good例子中的情况是:

  • 在最后一次提取操作后返回 false,这是因为 int 提取运算符必须读取,直到找到一个非数字字符。在这种情况下,这是 EOF 字符,当读取该字符时 即使作为分隔符 设置流的 eofbit,导致 good 失败
  • 调用read后返回true,因为read精确提取sizeof(int)字节,所以即使EOF字符是下一个字符,它也不会被读取,留下流的 eofbit 未设置并且 good 通过

peek 可以在其中任何一个之后使用,并且在这两种情况下都会正确 return char_traits<char>::eof() 。实际上,这是为您检查缓冲区,但二进制文件有一个重要区别:如果您自己检查二进制文件,您会发现它可能 包含 EOF 字符。 (在大多数定义为 0xFF 的系统上,其中 4 个以 -1 的二进制表示形式表示。)如果您正在检查缓冲区的下一个字符,您将不知道它是否实际上是文件的末尾。

peek 不只是 return 和 char,它 return 是 int_type。如果 peek returns 0x000000FF 那么您正在查看 EOF 字符,但 不是 文件末尾。如果 peek returns char_traits<char>::eof()(通常为 0xFFFFFFFF),那么您正在查看文件的末尾。