c++程序在读取大二进制文件时放弃



我使用来自MNIST网站的文件作为示例;具体来说,是t10k-images-idx3-ubyte.gz。为了重现我的问题,下载该文件并解压缩它,你应该得到一个名为t10k-images.idx3-ubyte的文件,这是我们正在读取的文件。

我的问题是,当我试图从这个文件在一个大块中读取字节时,它似乎读取了一点,然后放弃了。下面是一段c++代码,它尝试一次读取(几乎)所有的文件,然后将其转储到一个文本文件中以进行调试。(请原谅我使用了不必要的#include。)对于上下文,该文件是一个二进制文件,其前16个字节是幻数,这就是我在读取它之前查找第16个字节的原因。字节16到末尾是10,000张大小为28 × 28的图像的原始灰度像素值。

#include <array>
#include <iostream>
#include <fstream>
#include <string>
#include <exception>
#include <vector>
int main() {
try {
std::string path = R"(Drive:PathTot10k-images.idx3-ubyte)";
std::ifstream inputStream {path};
inputStream.seekg(16);  // Skip the magic numbers at the beginning.
char* arrayBuffer = new char[28 * 28 * 10000];  // Allocate memory for 10,000 greyscale images of size 28 x 28.
inputStream.read(arrayBuffer, 28 * 28 * 10000);
std::ofstream output {R"(Drive:PathToPixelBuffer.txt)"};  // Output file for debugging.
for (size_t i = 0; i < 28 * 28 * 10000; i++) {
output << static_cast<short>(arrayBuffer[i]);
// Below prints a new line after every 28 pixels.
if ((i + 1) % 28 == 0) {
output << "n";
}
else {
output << " ";
}
}
std::cout << inputStream.good() << std::endl;
std::cout << "WTF?" << std::endl;  // LOL. I just use this to check that everything's actually been executed, because sometimes the program shits itself and quits silently.
delete[] arrayBuffer;
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
} catch (...) {
std::cout << "WTF happened!?!?" << std::endl;
}
return 0;
}

当您运行代码(在适当地修改路径之后)并检查输出文本文件时,您将看到文件最初包含来自文件的合法字节值(介于-128和127之间的整数,但大部分为0),但是当您向下滚动时,您将发现在一些合法值之后,打印的值变得完全相同(在我的情况下,出于某种原因,要么全为0,要么全为-51)。您在计算机上看到的可能会有所不同,但在任何情况下,它们都是我认为未初始化的字节。因此,ifstream::read()似乎工作了一段时间,但很快放弃并停止阅读。我是不是漏掉了一些基本的东西?比如,我一次能读的字节数是否有我不知道的缓冲区限制?

编辑哦,顺便说一下,我用的是Windows。

关于打开二进制文件的OPs代码:

std::ifstream inputStream {path};

应该是:

std::ifstream inputStream(path, std::ios::binary);

这是Windows上常见的陷阱:

文件流应该用std::ios::binary来读写二进制文件。

关于这个话题,cppreference.com有一个很好的解释:

二进制和文本模式

文本流是一个有序的字符序列,可以组成行;一行可以分解为零个或多个字符加上一个终止的'n'(" newline ")字符。最后一行是否需要终止'n'是由实现定义的。此外,为了符合操作系统中表示文本的约定,可能需要在输入和输出时添加、修改或删除字符(特别是,Windows操作系统上的C流在输出时将'n'转换为'rn',并在输入时将'rn'转换为'n')。

只有满足以下条件,才能保证从文本流中读入的数据与之前写进文本流的数据相等:

  • 数据仅由打印字符和/或控制字符't''n'组成(特别是,在Windows操作系统上,字符'x1A'终止输入)。
  • 没有'n'字符前面紧接空格字符(这些空格字符可能在以后作为输入读取输出时消失)。
  • 最后一个字符是'n'

一个二进制流是一个有序的字符序列,可以透明地记录内部数据。从二进制流读入的数据总是等于先前写入该流的数据,除了允许在流的末尾附加不确定数量的空字符。宽二进制流不需要在初始移位状态结束。

在任何平台上对二进制文件的流I/O使用std::ios::binary都是一个好主意。它对没有影响的平台(例如Linux)没有任何影响。

要读取二进制文件,您需要使用std::ifstream inputStream(path, std::ios_base::binary),否则可能会发生应用程序没有读取正确的内容。

所以,正确的代码是
#include <array>
#include <iostream>
#include <fstream>
#include <string>
#include <exception>
#include <vector>
int main() {
try {
std::string path = R"(Drive:PathTot10k-images.idx3-ubyte)";
std::ifstream inputStream (path, std::ios_base::binary);
inputStream.seekg(16);  // Skip the magic numbers at the beginning.
char* arrayBuffer = new char[28 * 28 * 10000];  // Allocate memory for 10,000 greyscale images of size 28 x 28.
inputStream.read(arrayBuffer, 28 * 28 * 10000);
std::ofstream output {R"(Drive:PathToPixelBuffer.txt)"};  // Output file for debugging.
for (size_t i = 0; i < 28 * 28 * 10000; i++) {
output << static_cast<short>(arrayBuffer[i]);
// Below prints a new line after every 28 pixels.
if ((i + 1) % 28 == 0) {
output << "n";
}
else {
output << " ";
}
}
std::cout << inputStream.good() << std::endl;
std::cout << "WTF?" << std::endl;  // LOL. I just use this to check that everything's actually been executed, because sometimes the program shits itself and quits silently.
delete[] arrayBuffer;
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
} catch (...) {
std::cout << "WTF happened!?!?" << std::endl;
}
return 0;
}

,这并不取决于平台(如Windows, Linux等)

最新更新