Wie lese ich eine Datei in eine std::string
, dh lese die gesamte Datei auf einmal?
Der Text- oder Binärmodus sollte vom Anrufer angegeben werden. Die Lösung sollte standardkonform, tragbar und effizient sein. Es sollte nicht unnötig die Daten der Zeichenfolge kopieren und eine Neuzuweisung von Speicher beim Lesen der Zeichenfolge vermeiden.
Eine Möglichkeit, dies zu tun, besteht darin, die Dateigröße anzugeben, die Größe zu ändern std::string
und fread()
in die std::string
's const_cast<char*>()
' zu ändern data()
. Dies erfordert, dass die std::string
Daten zusammenhängend sind, was vom Standard nicht verlangt wird, aber es scheint für alle bekannten Implementierungen der Fall zu sein. Was noch schlimmer ist, wenn die Datei im Textmodus gelesen wird, entspricht die std::string
Größe der Datei möglicherweise nicht der Größe der Datei.
Eine vollständig korrekte, standardkonforme und tragbare Lösung könnte unter Verwendung von std::ifstream
's rdbuf()
in a std::ostringstream
und von dort in a konstruiert werden std::string
. Dies könnte jedoch die Zeichenfolgendaten kopieren und / oder den Speicher unnötig neu zuweisen.
- Sind alle relevanten Standardbibliotheksimplementierungen intelligent genug, um unnötigen Overhead zu vermeiden?
- Gibt es einen anderen Weg, es zu tun?
- Habe ich eine versteckte Boost-Funktion verpasst, die bereits die gewünschte Funktionalität bietet?
void slurp(std::string& data, bool is_binary)
rdbuf
(der in der akzeptierten Antwort) ist nicht der schnellsteread
.Antworten:
Eine Möglichkeit besteht darin, den Stream-Puffer in einen separaten Speicher-Stream zu leeren und diesen dann in Folgendes zu konvertieren
std::string
:Das ist schön prägnant. Wie in der Frage erwähnt, führt dies jedoch eine redundante Kopie durch, und leider gibt es grundsätzlich keine Möglichkeit, diese Kopie zu entfernen.
Die einzige wirkliche Lösung, die redundante Kopien vermeidet, besteht leider darin, das Lesen manuell in einer Schleife durchzuführen. Da C ++ jetzt zusammenhängende Zeichenfolgen garantiert hat, könnte man Folgendes schreiben (≥C ++ 14):
quelle
string
. Das heißt, es wird doppelt so viel Speicher benötigt wie bei einigen anderen Optionen. (Es gibt keine Möglichkeit, den Puffer zu verschieben). Für eine große Datei wäre dies eine erhebliche Strafe, die möglicherweise sogar einen Zuordnungsfehler verursacht.Siehe diese Antwort auf eine ähnliche Frage.
Für Ihre Bequemlichkeit reposte ich die CTT-Lösung:
Diese Lösung führte zu etwa 20% schnelleren Ausführungszeiten als die anderen hier vorgestellten Antworten, wenn durchschnittlich 100 Läufe gegen den Text von Moby Dick (1,3 Millionen) durchgeführt wurden. Nicht schlecht für eine tragbare C ++ - Lösung, ich würde gerne die Ergebnisse der mmap'ing der Datei sehen;)
quelle
ifs.seekg(0, ios::end)
vorher anrufentellg
? kurz nach dem Öffnen ist ein Datei-Lesezeiger am Anfang und gibt sotellg
Null zurücknullptr
&bytes[0]
ios::ate
, also denke ich, dass eine Version mit expliziter Bewegung zum Ende besser lesbar wäreDie kürzeste Variante: Live On Coliru
Es erfordert den Header
<iterator>
.Es gab einige Berichte, dass diese Methode langsamer ist als die Vorbelegung und Verwendung der Zeichenfolge
std::istream::read
. Bei 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.quelle
Verwenden
oder etwas sehr Nahes. Ich habe keine offene stdlib-Referenz, um mich selbst zu überprüfen.
Ja, ich verstehe, dass ich die
slurp
Funktion nicht wie gewünscht geschrieben habe.quelle
operator>>
Einlesen in astd::basic_streambuf
wird der Eingabestream verbraucht (was davon übrig bleibt), sodass die Schleife nicht erforderlich ist.Wenn Sie C ++ 17 (std :: filesystem) haben, gibt es auch diesen Weg (der die Dateigröße
std::filesystem::file_size
anstelle vonseekg
und durchführttellg
):Hinweis : Möglicherweise müssen Sie C ++ 17 verwenden
<experimental/filesystem>
undstd::experimental::filesystem
wenn Ihre Standardbibliothek C ++ 17 noch nicht vollständig unterstützt. Sie könnten auch ersetzen , müssenresult.data()
mit ,&result[0]
wenn es nicht unterstützt nicht-const std :: basic_string Daten .quelle
boost::filesystem
dass Sie Boost auch verwenden können, wenn Sie nicht über C ++ 17 verfügenIch habe nicht genug Ruf, um Antworten mit direkt zu kommentieren
tellg()
.Bitte beachten Sie, dass
tellg()
bei einem Fehler -1 zurückgegeben werden kann. Wenn Sie das Ergebnistellg()
als Zuweisungsparameter übergeben, sollten Sie zuerst das Ergebnis überprüfen.Ein Beispiel für das Problem:
Wenn im obigen Beispiel
tellg()
ein Fehler auftritt, wird -1 zurückgegeben. Das implizite Umwandeln zwischen vorzeichenbehaftet (dh das Ergebnis vontellg()
) und vorzeichenlos (dh das Argument an denvector<char>
Konstruktor) führt dazu, dass Ihr Vektor fälschlicherweise eine sehr große Anzahl von Bytes zuweist. (Wahrscheinlich 4294967295 Bytes oder 4 GB.)Ändern der Antwort von paxos1977, um Folgendes zu berücksichtigen:
quelle
Diese Lösung fügt der rdbuf () - basierten Methode eine Fehlerprüfung hinzu.
Ich füge diese Antwort hinzu, da 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 (
str_stream << file_stream.rdbuf()
) von stringstream . Das Problem ist, dass dies das Failbit des Stringstreams festlegt, wenn keine Zeichen eingefügt werden. Dies kann an einem Fehler liegen oder daran, dass die Datei leer ist. Wenn Sie durch Überprüfen des Failbits nach Fehlern suchen, wird beim Lesen einer leeren Datei ein falsches Positiv angezeigt. Wie kann man das legitime Versagen beim Einfügen von Zeichen und das "Versagen" beim Einfügen von Zeichen, weil die Datei leer ist, eindeutig unterscheiden?Sie könnten denken, explizit nach einer leeren Datei zu suchen, aber das ist mehr Code und die damit verbundene Fehlerprüfung.
Das Überprüfen auf den Fehlerzustand
str_stream.fail() && !str_stream.eof()
funktioniert nicht, da durch die Einfügeoperation das Eofbit nicht festgelegt wird (weder im Ostringstream noch im Ifstream).Die Lösung besteht also darin, den Betrieb zu ändern. Verwenden Sie anstelle des Einfügeoperators (<<) von ostringstream den Extraktionsoperator (>>) von ifstream, mit dem das Eofbit festgelegt wird. Überprüfen Sie dann den Fehlerzustand
file_stream.fail() && !file_stream.eof()
.Wichtig ist, dass bei
file_stream >> str_stream.rdbuf()
einem legitimen Fehler niemals Eofbit eingestellt werden sollte (nach meinem Verständnis der Spezifikation). Dies bedeutet, dass die obige Prüfung ausreicht, um legitime Fehler zu erkennen.quelle
So etwas sollte nicht schlecht sein:
Der Vorteil hierbei ist, dass wir zuerst die Reserve machen, damit wir den String beim Einlesen nicht vergrößern müssen. Der Nachteil ist, dass wir es char für char machen. Eine intelligentere Version könnte den gesamten Lese-Buf erfassen und dann den Unterlauf aufrufen.
quelle
Hier ist eine Version, die die neue Dateisystembibliothek mit relativ robuster Fehlerprüfung verwendet:
quelle
infile.open
kann auch akzeptierenstd::string
ohne zu konvertieren mit.c_str()
filepath
ist keinstd::string
, es ist einstd::filesystem::path
. Es stellt sich herausstd::ifstream::open
, dass auch einer davon akzeptiert werden kann.std::filesystem::path
ist implizit konvertierbar zustd::string
::open
funktioniert die Member-Funktionstd::ifstream
, die akzeptiert, so,std::filesystem::path
als ob die::c_str()
Methode auf dem Pfad aufgerufen würde. Die Basis::value_type
der Pfade befindet sichchar
unter POSIX.Sie können die Funktion 'std :: getline' verwenden und 'eof' als Trennzeichen angeben. Der resultierende Code ist allerdings etwas dunkel:
quelle
Schreiben Sie niemals in den const char * -Puffer des std :: string. Niemals! Dies zu tun ist ein massiver Fehler.
Reservieren Sie () Speicherplatz für die gesamte Zeichenfolge in Ihrer std :: string, lesen Sie Blöcke aus Ihrer Datei von angemessener Größe in einen Puffer und fügen Sie sie hinzu (). Wie groß die Chunks sein müssen, hängt von der Größe Ihrer Eingabedatei ab. Ich bin mir ziemlich sicher, dass alle anderen tragbaren und STL-kompatiblen Mechanismen dasselbe tun (aber möglicherweise hübscher aussehen).
quelle
std::string
Puffer zu schreiben . und ich glaube, dass es bei allen tatsächlichen Implementierungen zuvor korrekt funktioniert hatstd::string::data()
Methode zum direkten Ändern des Zeichenfolgenpuffers, ohne auf Tricks wie zurückzugreifen&str[0]
.Verwendung:
quelle
Eine aktualisierte Funktion, die auf der CTT-Lösung aufbaut:
Es gibt zwei wichtige Unterschiede:
tellg()
Es wird nicht garantiert, dass der Offset in Bytes seit Beginn der Datei zurückgegeben wird. Stattdessen ist es, wie Puzomor Croatia betonte, eher ein Token, das innerhalb der fstream-Aufrufe verwendet werden kann.gcount()
Gibt jedoch die Anzahl der zuletzt formatierten unformatierten Bytes zurück. Wir öffnen daher die Datei, extrahieren und verwerfen den gesamten Inhalt mitignore()
, um die Größe der Datei zu ermitteln, und erstellen darauf basierend die Ausgabezeichenfolge.Zweitens müssen wir die Daten der Datei nicht von a
std::vector<char>
nach a kopieren, indem wirstd::string
direkt in die Zeichenfolge schreiben.In Bezug auf die Leistung sollte dies die absolut schnellste sein, indem die Zeichenfolge mit der entsprechenden Größe im Voraus zugewiesen und
read()
einmal aufgerufen wird. Interessanterweise wird die Verwendung vonignore()
undcountg()
anstelle vonate
undtellg()
auf gcc Stück für Stück auf fast dasselbe kompiliert .quelle
ifs.seekg(0)
stattifs.clear()
(dann funktioniert es).quelle