如何将二进制文件读入无符号字符的向量中

最近我被要求写一个函数,读取二进制文件到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::copytesting:

 ... 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 – 如果你想确认这个,尝试读取文件到一个字符数组,并比较实际的汇编代码。 除了找出向量中缓冲区的地址外,还有一些额外的工作,不应该有任何区别。

与往常一样,唯一的方法就是在案例中确定最有效的方法是衡量它。 “在互联网上询问”不是证据。