如何将二进制文件读入无符号字符的向量中
最近我被要求写一个函数,读取二进制文件到std::vector<BYTE>
其中BYTE
是一个unsigned char
。 很快,我来到这样的事情:
#include <fstream> #include <vector> typedef unsigned char BYTE; std::vector<BYTE> readFile(const char* filename) { // open the file: std::streampos fileSize; std::ifstream file(filename, std::ios::binary); // get its size: file.seekg(0, std::ios::end); fileSize = file.tellg(); file.seekg(0, std::ios::beg); // read the data: std::vector<BYTE> fileData(fileSize); file.read((char*) &fileData[0], fileSize); return fileData; }
这似乎是不必要的复杂和显式强制转换char*
,我不得不使用调用file.read
不会让我感觉好一点。
另一个select是使用std::istreambuf_iterator
:
std::vector<BYTE> readFile(const char* filename) { // open the file: std::ifstream file(filename, std::ios::binary); // read the data: return std::vector<BYTE>((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); }
这是非常简单和短暂的,但即使当我读入std::vector<unsigned char>
时,仍然必须使用std::istreambuf_iterator<char>
std::vector<unsigned char>
。
最后一个看起来很直接的选项是使用std::basic_ifstream<BYTE>
,它明确地表示“我想要一个input文件stream,我想用它来读取BYTE
s” :
std::vector<BYTE> readFile(const char* filename) { // open the file: std::basic_ifstream<BYTE> file(filename, std::ios::binary); // read the data: return std::vector<BYTE>((std::istreambuf_iterator<BYTE>(file)), std::istreambuf_iterator<BYTE>()); }
但我不确定在这种情况下basic_ifstream
是否是一个合适的select。
读取二进制文件到vector
的最佳方法是什么? 我也想知道“幕后”发生了什么,以及我可能遇到的问题是什么(除了stream没有正确打开,可以通过简单的is_open
检查来避免)。
有没有什么好的理由,为什么更喜欢在这里使用std::istreambuf_iterator
?
(我能看到的唯一优点是简单)
在testing性能时,我会包含一个testing用例:
std::vector<BYTE> readFile(const char* filename) { // open the file: std::ifstream file(filename, std::ios::binary); // Stop eating new lines in binary mode!!! file.unsetf(std::ios::skipws); // get its size: std::streampos fileSize; file.seekg(0, std::ios::end); fileSize = file.tellg(); file.seekg(0, std::ios::beg); // reserve capacity std::vector<BYTE> vec; vec.reserve(fileSize); // read the data: vec.insert(vec.begin(), std::istream_iterator<BYTE>(file), std::istream_iterator<BYTE>()); return vec; }
我的想法是方法1的构造函数触及vector
的元素,然后再read
每个元素。
方法2和方法3看起来最有希望,但可能遭受一个或多个resize
。 因此,在阅读或插入之前reserve
原因。
我也会用std::copy
testing:
... std::vector<byte> vec; vec.reserve(fileSize); std::copy(std::istream_iterator<BYTE>(file), std::istream_iterator<BYTE>(), std::back_inserter(vec));
最后,我认为最好的解决scheme将避免来自istream_iterator
operator >>
(以及operator >>
所有开销和善意,试图解释二进制数据)。 但是我不知道该使用什么,可以直接将数据复制到vector中。
最后,我用二进制数据testing显示ios::binary
不被尊重。 因此来自<iomanip>
noskipws
的原因。
std::ifstream stream("mona-lisa.raw", std::ios::in | std::ios::binary); std::vector<uint8_t> contents((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>()); for(auto i: contents) { int value = i; std::cout << "data: " << value << std::endl; } std::cout << "file size: " << contents.size() << std::endl;
由于您将整个文件加载到内存中,所以最优化的版本是将文件映射到内存中。 这是因为内核无论如何都会将文件加载到内核页面caching中,并且通过映射文件,您只需将caching中的这些页面展示到您的进程中。 也被称为零拷贝。
当你使用std::vector<>
它将内核页面caching中的数据复制到std::vector<>
,当你只想读取文件时这是不必要的。
另外,当将两个input迭代器传递给std::vector<>
它会在读取时增长缓冲区,因为它不知道文件大小。 当首先将std::vector<>
大小调整为文件大小时,它不必要地将其内容清零,因为它将被文件数据覆盖。 这两种方法在空间和时间上都是次优的。
我会认为,第一种方法,使用大小和使用stream::read()
将是最有效的。 对char *
进行转换的“代价”很可能是零 – 这种types的转换只是告诉编译器:“嘿,我知道你认为这是一种不同的types,但是我真的希望这种types在这里…”不添加任何额外的instrucitons – 如果你想确认这个,尝试读取文件到一个字符数组,并比较实际的汇编代码。 除了找出向量中缓冲区的地址外,还有一些额外的工作,不应该有任何区别。
与往常一样,唯一的方法就是在案例中确定最有效的方法是衡量它。 “在互联网上询问”不是证据。