Як читати двійковий файл у вектор без підписаних символів


76

Нещодавно мене попросили написати функцію, яка зчитує двійковий файл у 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>, яке начебто прямо виражає, що "я хочу потік вхідного файлу, і я хочу використовувати його для читання BYTEs" :

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
@ R.MartinhoFernandes: Я мав на увазі, що 3-й варіант, здається, не є нічим кращим, ніж 2-й варіант.
LihO

хтось виміряв це (у 2011 році), принаймні для завантаження у рядок. insanecoding.blogspot.hk/2011/11/how-to-read-in-file-in-c.html
jiggunjer

Більш безпечний спосіб знайти розмір: скористайтеся спеціальним ignore() рахунком: file.ignore(std::numeric_limits<std::streamsize>::max());і поверніть std::streamsize"витягнуте", використовуючиauto size =file.gcount();
Бретт Хейл

Відповіді:


45

Під час тестування на продуктивність я б включив тестовий приклад для:

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>.


Чи є спосіб прочитати певний розмір у масиві замість цілого файлу, як описано тут?
супергерой

1
Я думав, вам потрібно лише за file.unsetf(std::ios::skipws);умови використання оператора >>
jiggunjer

Мені потрібно було file.unsetf(std::ios::skipws);навіть при використанні std::copyдля копіювання в a vector, інакше я втратив би дані. Це було з Boost 1.53.0.
Фенікс

1
@jiggunjer std::istream_iteratorвикористовує >>оператор внутрішньо для вилучення даних із потоку.
tomi.lee.jones

Спробував більше 8 фрагментів, жоден з них не працював, але це, дякую! +1
MikeTheCoder

17
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;

7

Оскільки ви завантажуєте весь файл в пам'ять, найоптимальнішою версією є відображення файлу в пам'ять. Це тому, що ядро ​​все одно завантажує файл у кеш сторінок ядра, і, зіставивши файл, ви просто виставляєте ці сторінки в кеші у свій процес. Також відомий як нульова копія.

При його використанні std::vector<>копіюються дані з кешу сторінки ядра, std::vector<>що є непотрібним, коли ви просто хочете прочитати файл.

Крім того, при передачі двох вхідних ітераторів std::vector<>він збільшує свій буфер під час читання, оскільки він не знає розмір файлу. При зміні розміру std::vector<>файлу спочатку він без потреби обнуляє його вміст, оскільки він все одно буде перезаписаний даними файлу. Обидва методи є неоптимальними з точки зору простору та часу.


Так, якщо вміст не повинен бути у векторі, це, безумовно, найкращий метод.
Mats Petersson

а не resize, reserveне ініціалізується.
jiggunjer

це означає, що ви можете передати ітератори у зарезервований вектор, щоб уникнути зайвої зміни розміру. Посилаючись на ваш останній абзац.
jiggunjer

1
@jiggunjer Ну, це не спрацювало б, оскільки ви не можете отримати доступ до зарезервованої ємності без попереднього зміни розміру вектора.
Максим Єгорушкін

1
Для тих, хто читає без посилання на стандарт, це незрозуміло. Це не пояснює, як зіставити в пам’ять - я припускаю streambuf і basicце робити ?. Крім того, термінологія припускає, що Linux / UNIX є використовуваною ОС, і, схоже, вона може бути застосована не для всіх платформ - чи існують однакові концепції та найкращі практики у всіх ОС, на які можна орієнтуватись за допомогою C ++?
underscore_d

3

Я міг би подумати, що перший метод із використанням розміру та використання stream::read()буде найбільш ефективним. "Вартість" кастингу char *, швидше за все, дорівнює нулю - касти такого роду просто повідомляють компілятору, що "Ей, я знаю, ти вважаєш, що це інший тип, але я справді хочу цей тип тут ...", і не додає будь-які додаткові інструкції - якщо ви хочете це підтвердити, спробуйте прочитати файл у масиві символів і порівняйте фактичний код асемблера. Окрім трохи додаткової роботи, щоб з’ясувати адресу буфера всередині вектора, ніякої різниці бути не повинно.

Як завжди, єдиний спосіб точно сказати у ВАШОМУ ВИПАДКУ, що є найбільш ефективним, - це виміряти його. "Розпитування в Інтернеті" не є доказом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.