it-swarm.com.de

Was ist der beste Weg, um eine ganze Datei in eine std :: string in C ++ zu lesen?

Wie lese ich eine Datei in einen std::string ein, d. H. Lese die gesamte Datei auf einmal?

Text- oder Binärmodus sollte vom Aufrufer angegeben werden. Die Lösung sollte standardkonform, portabel und effizient sein. Die Daten des Strings sollten nicht unnötig kopiert werden, und es sollte vermieden werden, dass beim Lesen des Strings Speicher neu zugewiesen wird.

Eine Möglichkeit, dies zu tun, besteht darin, die Dateigröße zu bestimmen, den std::string und fread() in die const_cast<char*>() 'ed data() des std::string zu ändern. Dies erfordert, dass die Daten von std::string zusammenhängend sind, was vom Standard nicht verlangt wird, aber es scheint, dass dies für alle bekannten Implementierungen der Fall ist. Was schlimmer ist, wenn die Datei im Textmodus gelesen wird, stimmt die Größe des std::string möglicherweise nicht mit der Größe der Datei überein.

Eine vollständig korrekte, standardkonforme und tragbare Lösung könnte unter Verwendung von std::ifstreams rdbuf() in einen std::ostringstream und von dort in einen std::string erstellt werden. Dies könnte jedoch die Zeichenfolgendaten kopieren und/oder unnötigerweise Speicher neu zuweisen. Sind alle relevanten Standard-Bibliotheksimplementierungen intelligent genug, um unnötigen Overhead zu vermeiden? Gibt es eine andere Möglichkeit? Habe ich eine versteckte Boost-Funktion verpasst, die bereits die gewünschte Funktionalität bietet?

Bitte zeigen Sie Ihrem Vorschlag, wie er umgesetzt werden soll.

void Slurp(std::string& data, bool is_binary)

unter Berücksichtigung der obigen Diskussion.

149
wilbur_m

Und das schnellste (von dem ich weiß, dass es speicherabgebildete Dateien rabattiert):

std::string str(static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str());

Dies erfordert den zusätzlichen Header <sstream> Für den String-Stream. (Der static_cast Ist erforderlich, da operator << Einen einfachen alten ostream& Zurückgibt, aber wir wissen, dass es sich in Wirklichkeit um einen stringstream& Handelt, sodass die Besetzung sicher ist.)

Wenn Sie das Temporäre in mehrere Zeilen aufteilen und in eine Variable verschieben, erhalten Sie einen besser lesbaren Code:

std::string Slurp(std::ifstream& in) {
    std::stringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}

Oder noch einmal in einer einzigen Zeile:

std::string Slurp(std::ifstream& in) {
    return static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str();
}
125
Konrad Rudolph

Siehe diese Antwort zu einer ähnlichen Frage.

Zur Vereinfachung stelle ich die CTT-Lösung erneut bereit:

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(bytes.data(), fileSize);

    return string(bytes.data(), fileSize);
}

Diese Lösung führte zu etwa 20% schnelleren Ausführungszeiten als die anderen hier vorgestellten Antworten, wenn im Durchschnitt 100 Läufe gegen den Text von Moby Dick (1,3 Millionen) ausgeführt wurden. Nicht schlecht für eine tragbare C++ - Lösung, ich würde gerne die Ergebnisse von mmap'ing der Datei sehen;)

48
paxos1977

Die kürzeste Variante: Live On Colir

std::string str(std::istreambuf_iterator<char>{ifs}, {});

Es benötigt den Header <iterator>.

Es gab Berichte, dass diese Methode langsamer ist als die Vorbelegung der Zeichenfolge und die Verwendung von std::istream::read. Auf einem modernen Compiler mit aktivierten Optimierungen scheint dies jedoch nicht mehr der Fall zu sein, obwohl die relative Leistung verschiedener Methoden stark vom Compiler abhängig zu sein scheint.

43
Konrad Rudolph

Verwenden

#include <iostream>
#include <sstream>
#include <fstream>

int main()
{
  std::ifstream input("file.txt");
  std::stringstream sstr;

  while(input >> sstr.rdbuf());

  std::cout << sstr.str() << std::endl;
}

oder etwas sehr Nahes. Ich habe keine stdlib-Referenz geöffnet, um mich selbst zu überprüfen.

Ja, ich habe verstanden, dass ich die Slurp -Funktion nicht wie gewünscht geschrieben habe.

16
Ben Collins

Ich habe nicht genug Reputation, um Antworten mit tellg() direkt zu kommentieren.

Bitte beachten Sie, dass tellg() im Fehlerfall -1 zurückgeben kann. Wenn Sie das Ergebnis von tellg() als Zuordnungsparameter übergeben, sollten Sie zuerst das Ergebnis überprüfen.

Ein Beispiel für das Problem:

...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...

Wenn im obigen Beispiel tellg() auf einen Fehler stößt, wird -1 zurückgegeben. Implizites Casting zwischen vorzeichenbehafteten (dh dem Ergebnis von tellg()) und vorzeichenlosen (dh dem Argument an den Konstruktor vector<char>) Führt dazu, dass Ihr Vektor fälschlicherweise ein very zuweist große Anzahl von Bytes. (Möglicherweise 4294967295 Byte oder 4 GB.)

Ändern der Antwort von paxos1977, um das oben Gesagte zu berücksichtigen:

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    if (fileSize < 0)                             <--- ADDED
        return std::string();                     <--- ADDED

    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(&bytes[0], fileSize);

    return string(&bytes[0], fileSize);
}
12
Rick Ramstetter

Wenn Sie C++ 17 (std :: filesystem) haben, gibt es auch diesen Weg (der die Dateigröße durch std::filesystem::file_size Anstelle von seekg und tellg ermittelt):

#include <filesystem>
#include <fstream>
#include <string>

namespace fs = std::filesystem;

std::string readFile(fs::path path)
{
    // Open the stream to 'lock' the file.
    std::ifstream f{ path };

    // Obtain the size of the file.
    const auto sz = fs::file_size(path);

    // Create a buffer.
    std::string result(sz, ' ');

    // Read the whole file into the buffer.
    f.read(result.data(), sz);

    return result;
}

Hinweis : Möglicherweise müssen Sie <experimental/filesystem> Und std::experimental::filesystem Verwenden, wenn Ihre Standardbibliothek C + noch nicht vollständig unterstützt +17. Möglicherweise müssen Sie result.data() durch &result[0] Ersetzen, wenn dies nicht unterstützt wird nicht konstante std :: basic_string-Daten .

5
Gabriel M

Diese Lösung fügt der auf rdbuf () basierenden Methode eine Fehlerprüfung hinzu.

std::string file_to_string(const std::string& file_name)
{
    std::ifstream file_stream{file_name};

    if (file_stream.fail())
    {
        // Error opening file.
    }

    std::ostringstream str_stream{};
    file_stream >> str_stream.rdbuf();  // NOT str_stream << file_stream.rdbuf()

    if (file_stream.fail() && !file_stream.eof())
    {
        // Error reading file.
    }

    return str_stream.str();
}

Ich füge diese Antwort hinzu, weil das Hinzufügen von Fehlerprüfungen zur ursprünglichen Methode nicht so trivial ist, wie Sie es erwarten würden. Die ursprüngliche Methode verwendet den Einfügeoperator von stringstream (str_stream << file_stream.rdbuf()). Das Problem ist, dass dies das Failbit des Stringstreams setzt, wenn keine Zeichen eingefügt werden. Das kann an einem Fehler liegen oder daran, dass die Datei leer ist. Wenn Sie das Failbit auf Fehler überprüfen, wird beim Lesen einer leeren Datei ein falsches Positiv angezeigt. Wie kann man ein legitimes Versagen beim Einfügen von Zeichen und ein "Versagen" beim Einfügen von Zeichen, weil die Datei leer ist, disambiguieren?

Möglicherweise möchten Sie explizit nach einer leeren Datei suchen, dies ist jedoch mehr Code und die damit verbundene Fehlerprüfung.

Die Überprüfung auf die Fehlerbedingung str_stream.fail() && !str_stream.eof() funktioniert nicht, da der Einfügevorgang das eofbit nicht festlegt (im Ostringstream oder im Ifstream).

Die Lösung besteht also darin, den Betrieb zu ändern. Verwenden Sie anstelle des Einfügeoperators von ostringstream (<<) den Extraktionsoperator von ifstream (>>), der das eofbit festlegt. Überprüfen Sie dann die Fehlerbedingung file_stream.fail() && !file_stream.eof().

Wenn file_stream >> str_stream.rdbuf() auf einen legitimen Fehler stößt, sollte es (nach meinem Verständnis der Spezifikation) niemals eofbit setzen. Dies bedeutet, dass die obige Prüfung ausreicht, um berechtigte Fehler zu erkennen.

3
tgnottingham

Schreiben Sie niemals in den const char * -Puffer von std :: string. Niemals! Dies zu tun ist ein schwerwiegender Fehler.

Reservieren Sie () Platz für die gesamte Zeichenkette in Ihrer std :: Zeichenkette, lesen Sie Stücke aus Ihrer Datei von angemessener Größe in einen Puffer und hängen Sie () ihn an. Wie groß die Chunks sein müssen, hängt von Ihrer Eingabedateigröße ab. Ich bin mir ziemlich sicher, dass alle anderen tragbaren und STL-kompatiblen Mechanismen dasselbe tun werden (und trotzdem hübscher aussehen können).

3
Thorsten79

So etwas sollte nicht so schlimm sein:

void Slurp(std::string& data, const std::string& filename, bool is_binary)
{
    std::ios_base::openmode openmode = ios::ate | ios::in;
    if (is_binary)
        openmode |= ios::binary;
    ifstream file(filename.c_str(), openmode);
    data.clear();
    data.reserve(file.tellg());
    file.seekg(0, ios::beg);
    data.append(istreambuf_iterator<char>(file.rdbuf()), 
                istreambuf_iterator<char>());
}

Der Vorteil hierbei ist, dass wir zuerst die Reserve machen, damit wir die Zeichenfolge beim Einlesen nicht vergrößern müssen. Der Nachteil ist, dass wir es char by char machen. Eine intelligentere Version könnte den gesamten Lesefehler erfassen und dann underflow aufrufen.

3
Matt Price

Sie können die Funktion 'std :: getline' verwenden und 'eof' als Trennzeichen angeben. Der resultierende Code ist allerdings ein bisschen dunkel:

std::string data;
std::ifstream in( "test.txt" );
std::getline( in, data, std::string::traits_type::to_char_type( 
                  std::string::traits_type::eof() ) );
2
Martin Cote