Как читать двоичный файл в вектор беззнаковых символов
В последнее время мне было предложено написать функцию, которая читает двоичный файл в 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
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
является подходящим выбором в этом случае.
Каков наилучший способ чтения двоичного файла в vector
? Я также хотел бы знать, что происходит "за сценой" и каковы возможные проблемы, с которыми я мог столкнуться (кроме потока, который не открывается должным образом, чего можно избежать простым is_open
).
Есть ли веская причина, почему вы предпочитаете использовать std::istreambuf_iterator
здесь?
(единственное преимущество, которое я вижу, это простота)
Ответы
Ответ 1
При тестировании производительности я бы включил тестовый пример для:
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 >>
от istream_iterator
(и все накладные расходы и доброта от operator >>
, пытаясь интерпретировать двоичные данные). Но я не знаю, что использовать, что позволяет вам напрямую копировать данные в вектор.
Наконец, мое тестирование двоичными данными показывает, что ios::binary
не выполняется. Отсюда причина noskipws
из <iomanip>
.
Ответ 2
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;
Ответ 3
Поскольку вы загружаете весь файл в память, наиболее оптимальной версией является отображение файла в память. Это связано с тем, что ядро загружает файл в кеш файл ядра в любом случае и путем сопоставления файла, который вы просто выставляете эти страницы в кеше в свой процесс. Также известен как нуль-копия.
Когда вы используете std::vector<>
, он копирует данные из кеша страницы ядра в std::vector<>
, что не нужно, когда вы просто хотите прочитать файл.
Кроме того, при передаче двух итераторов ввода на std::vector<>
он увеличивает свой буфер при чтении, потому что он не знает размер файла. При изменении размера файла std::vector<>
на размер файла сначала он без необходимости обнуляет содержимое, потому что он все равно будет перезаписан файловыми данными. Оба метода являются субоптимальными с точки зрения пространства и времени.
Ответ 4
Я бы подумал, что первый метод, используя размер и используя stream::read()
, будет наиболее эффективным. "Стоимость" кастинга до char *
, скорее всего, равна нулю. Каста такого рода просто сообщает компилятору, что "Эй, я знаю, вы думаете, что это другой тип, но я действительно хочу этот тип здесь...", и не добавляет никаких дополнительных инструкций - если вы хотите это подтвердить, попробуйте прочитать файл в массив char и сравнить фактический код ассемблера. Помимо небольшого количества дополнительной работы, чтобы выяснить адрес буфера внутри вектора, не должно быть никакой разницы.
Как всегда, единственный способ точно сказать, что в вашем случае наиболее эффективным является его измерение. "Просить в Интернете" не является доказательством.