最近我被要求编写一个函数,将二进制文件读入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
的显式强制转换并不会让我感觉更好。
另一种选择是使用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::istreambuf_iterator<char>
,我仍然必须使用std::vector<unsigned char>
。
最后一个看似非常简单的选项就是使用std::basic_ifstream<BYTE>
,它明确地表达了“我想要一个输入文件流,我想用它来阅读BYTE
的“:
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
在这种情况下是否合适。
将二进制文件读入vector
的最佳方式是什么?我也想知道“幕后”发生了什么我可能遇到的问题是什么(除了流未正确打开,可以通过简单的is_open
检查来避免)。
为什么人们更愿意在这里使用std::istreambuf_iterator
?
(我能看到的唯一优势是简单性)
答案 0 :(得分:26)
在测试性能时,我会包含一个测试用例:
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
:
...
std::vector<byte> vec;
vec.reserve(fileSize);
std::copy(std::istream_iterator<BYTE>(file),
std::istream_iterator<BYTE>(),
std::back_inserter(vec));
最后,我认为最好的解决方案将避免来自operator >>
的{{1}}(以及istream_iterator
试图解释二进制数据的所有开销和善意)。但我不知道如何使用它可以直接将数据复制到矢量中。
最后,我对二进制数据的测试显示operator >>
未被尊重。因此ios::binary
来自noskipws
的原因。
答案 1 :(得分:12)
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;
答案 2 :(得分:6)
由于您要将整个文件加载到内存中,因此最佳版本是将文件映射到内存中。这是因为内核无论如何都要将文件加载到内核页面缓存中,并通过映射文件将缓存中的那些页面暴露到您的进程中。也称为零拷贝。
使用std::vector<>
时,它会将内核页面缓存中的数据复制到std::vector<>
,这在您只想读取文件时是不必要的。
此外,当将两个输入迭代器传递给std::vector<>
时,它会在读取时增大其缓冲区,因为它不知道文件大小。首先将std::vector<>
的大小调整为文件大小时,它会不必要地将其内容清零,因为无论如何它都会被文件数据覆盖。这两种方法在空间和时间方面都是次优的。
答案 3 :(得分:3)
我原以为第一种使用大小并使用stream::read()
的方法效率最高。投射到char *
的“成本”很可能是零 - 这种类型的演员只是告诉编译器“嘿,我知道你认为这是一个不同的类型,但我真的想要这种类型......” ,并且不添加任何额外的指令 - 如果您想确认这一点,请尝试将文件读入char数组,并比较实际的汇编代码。除了一些额外的工作来计算向量内的缓冲区的地址,应该没有任何区别。
与往常一样,唯一可以确定在您的情况下最有效的方法是测量它。 “在互联网上询问”并不是证明。