我使用来自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有一个很好的解释:在任何平台上对二进制文件的流I/O使用二进制和文本模式
文本流是一个有序的字符序列,可以组成行;一行可以分解为零个或多个字符加上一个终止的
'n'
(" newline ")字符。最后一行是否需要终止'n'
是由实现定义的。此外,为了符合操作系统中表示文本的约定,可能需要在输入和输出时添加、修改或删除字符(特别是,Windows操作系统上的C流在输出时将'n'
转换为'rn'
,并在输入时将'rn'
转换为'n'
)。只有满足以下条件,才能保证从文本流中读入的数据与之前写进文本流的数据相等:
- 数据仅由打印字符和/或控制字符
't'
和'n'
组成(特别是,在Windows操作系统上,字符' x1A'
终止输入)。- 没有
'n'
字符前面紧接空格字符(这些空格字符可能在以后作为输入读取输出时消失)。- 最后一个字符是
'n'
。一个二进制流是一个有序的字符序列,可以透明地记录内部数据。从二进制流读入的数据总是等于先前写入该流的数据,除了允许在流的末尾附加不确定数量的空字符。宽二进制流不需要在初始移位状态结束。
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等)