Подтвердить что ты не робот

Как читать двоичный файл в вектор беззнаковых символов

В последнее время мне было предложено написать функцию, которая читает двоичный файл в 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 здесь?
(единственное преимущество, которое я вижу, это простота)

4b9b3361

Ответ 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 и сравнить фактический код ассемблера. Помимо небольшого количества дополнительной работы, чтобы выяснить адрес буфера внутри вектора, не должно быть никакой разницы.

Как всегда, единственный способ точно сказать, что в вашем случае наиболее эффективным является его измерение. "Просить в Интернете" не является доказательством.