So lesen Sie eine Binärdatei in einen Vektor mit vorzeichenlosen Zeichen

75

In letzter Zeit wurde ich gebeten, eine Funktion zu schreiben, die die Binärdatei in das std::vector<BYTE>wo BYTEist ein liest unsigned char. Ganz schnell kam ich mit so etwas:

#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;
}

Das scheint unnötig kompliziert zu sein, und die explizite Besetzung, char*die ich beim Telefonieren file.readverwenden musste, lässt mich nicht besser fühlen.


Eine andere Option ist 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>());
}

Das ist ziemlich einfach und kurz, aber ich muss es trotzdem benutzen, std::istreambuf_iterator<char>auch wenn ich hineinlese std::vector<unsigned char>.


Die letzte Option, die vollkommen unkompliziert zu sein scheint, ist die Verwendung std::basic_ifstream<BYTE>, die es ausdrücklich ausdrückt: "Ich möchte einen Eingabedateistream und ich möchte ihn zum Lesen von BYTEs verwenden" :

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>());
}

Ich bin mir aber nicht sicher, ob basic_ifstreamdies in diesem Fall eine angemessene Wahl ist.

Was ist der beste Weg, um eine Binärdatei in die zu lesen vector? Ich würde auch gerne wissen, was "hinter den Kulissen" passiert und auf welche möglichen Probleme ich stoßen könnte (abgesehen davon, dass der Stream nicht richtig geöffnet wird, was durch einfache is_openÜberprüfung vermieden werden könnte ).

Gibt es einen guten Grund, warum man es vorziehen würde, std::istreambuf_iteratorhier zu verwenden ?
(Der einzige Vorteil, den ich sehen kann, ist die Einfachheit)

LihO
quelle
1
@ R.MartinhoFernandes: Was ich damit gemeint habe war, dass die 3. Option nicht besser zu sein scheint als die 2. Option.
LihO
jemand hat es gemessen (im Jahr 2011), zumindest um es in einen String zu laden. insanecoding.blogspot.hk/2011/11/how-to-read-in-file-in-c.html
jiggunjer
Ein sicherer Weg, um die Größe zu finden: Verwenden Sie die spezielle ignore() Anzahl: file.ignore(std::numeric_limits<std::streamsize>::max());und geben Sie die std::streamsize'extrahierte' mitauto size =file.gcount();
Brett Hale

Antworten:

44

Beim Testen der Leistung würde ich einen Testfall hinzufügen für:

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

Meiner Meinung nach berührt der Konstruktor von Methode 1 die Elemente in vectorund dann readjedes Element erneut.

Methode 2 und Methode 3 sehen am vielversprechendsten aus, könnten aber eine oder mehrere leiden resize. Daher der Grund reservevor dem Lesen oder Einfügen.

Ich würde auch testen mit std::copy:

...
std::vector<byte> vec;
vec.reserve(fileSize);

std::copy(std::istream_iterator<BYTE>(file),
          std::istream_iterator<BYTE>(),
          std::back_inserter(vec));

Am Ende, ich denke , die beste Lösung vermeiden wird operator >>aus istream_iterator(und alle Unkosten und Güte aus operator >>versuchen , binären Daten zu interpretieren). Aber ich weiß nicht, was ich verwenden soll, damit Sie die Daten direkt in den Vektor kopieren können.

Schließlich zeigt mein Test mit Binärdaten, ios::binarydass er nicht gewürdigt wird. Daher der Grund für noskipwsvon <iomanip>.

jww
quelle
Gibt es eine Möglichkeit, eine bestimmte Größe anstelle der gesamten hier beschriebenen Datei in das Array einzulesen?
Superheld
1
Ich dachte, Sie brauchen nur, file.unsetf(std::ios::skipws);wenn Sie den Operator >>
jiggunjer
Ich brauchte file.unsetf(std::ios::skipws);sogar beim std::copyKopieren auf ein vector, sonst würde ich Daten verlieren. Dies war mit Boost 1.53.0.
Phoenix
1
@jiggunjer std::istream_iteratorverwendet den >>Operator intern, um Daten aus dem Stream zu extrahieren.
Tomi.lee.jones
Versuchte mehr als 8 Schnipsel, keiner von ihnen hat funktioniert, aber dies, vielen Dank! +1
MikeTheCoder
16
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;
neoneye
quelle
7

Da Sie die gesamte Datei in den Speicher laden, ist es am besten, die Datei in den Speicher abzubilden. Dies liegt daran, dass der Kernel die Datei ohnehin in den Kernel-Seiten-Cache lädt und Sie durch Zuordnen der Datei nur diese Seiten im Cache in Ihren Prozess einbinden. Wird auch als Nullkopie bezeichnet.

Wenn Sie es verwenden std::vector<>, werden die Daten aus dem Kernel-Seiten-Cache kopiert, std::vector<>was nicht erforderlich ist, wenn Sie nur die Datei lesen möchten.

Wenn zwei Eingabe-Iteratoren übergeben werden, std::vector<>wird der Puffer beim Lesen vergrößert, da die Dateigröße nicht bekannt ist. Wenn std::vector<>die Größe zuerst auf die Dateigröße geändert wird, wird der Inhalt unnötig auf Null gesetzt, da er ohnehin mit Dateidaten überschrieben wird. Beide Methoden sind räumlich und zeitlich nicht optimal.

Maxim Egorushkin
quelle
Ja, wenn sich der Inhalt nicht in einem Vektor befinden muss, ist dies definitiv die beste Methode.
Mats Petersson
statt resize, reserveinitialize tut.
Jiggunjer
Dies bedeutet, dass Sie die Iteratoren an einen reservierten Vektor übergeben können, um redundante Größenänderungen zu vermeiden. Bezugnehmend auf Ihren letzten Absatz.
Jiggunjer
1
@jiggunjer Nun, das würde nicht funktionieren, da Sie nicht auf die reservierte Kapazität zugreifen können, ohne zuerst die Größe des Vektors zu ändern.
Maxim Egorushkin
1
Für jemanden, der ohne Bezugnahme auf den Standard liest, ist dies unklar. Es erklärt nicht, wie man sich dem Speicher zuordnet - ich nehme an streambuf und basicmache das?. Die Terminologie geht auch davon aus, dass Linux / UNIX das verwendete Betriebssystem ist, was möglicherweise nicht für alle Plattformen gilt. Gibt es in allen Betriebssystemen, auf die C ++ abzielt, dieselben Konzepte und Best Practices?
underscore_d
3

Ich hätte gedacht, dass die erste Methode, die Größe und Verwendung stream::read()zu verwenden, die effizienteste wäre. Die "Kosten" für das Casting char *sind höchstwahrscheinlich Null - Casts dieser Art sagen dem Compiler einfach, dass "Hey, ich weiß, dass Sie denken, dass dies ein anderer Typ ist, aber ich möchte diesen Typ wirklich hier ..." und fügt nichts hinzu Zusätzliche Anweisungen - Wenn Sie dies bestätigen möchten, lesen Sie die Datei in ein char-Array und vergleichen Sie den tatsächlichen Assembler-Code. Abgesehen von ein wenig zusätzlicher Arbeit, um die Adresse des Puffers innerhalb des Vektors herauszufinden, sollte es keinen Unterschied geben.

Wie immer ist die einzige Möglichkeit, in Ihrem Fall sicher zu sagen, was am effizientesten ist, die Messung. "Fragen im Internet" ist kein Beweis.

Mats Petersson
quelle