Hoe lees ik een heel bestand in een std::string in C++?

Hoe lees ik een bestand in een std::string, d.w.z. lees ik het hele bestand in één keer?

Tekst- of binaire modus moet worden opgegeven door de beller. De oplossing moet normconform, draagbaar en efficiënt zijn. Het mag de gegevens van de string niet onnodig kopiëren en het moet voorkomen dat geheugen opnieuw wordt toegewezen tijdens het lezen van de string.

Een manier om dit te doen is door de bestandsgrootte vast te stellen, de grootte van de std::stringen fread()te wijzigen in de std::string‘s const_cast<char*>()‘ed data(). Dit vereist dat de gegevens van de std::stringaaneengesloten zijn, wat niet vereist is door de standaard, maar het lijkt het geval te zijn voor alle bekende implementaties. Wat erger is, als het bestand in tekstmodus wordt gelezen, is de grootte van de std::stringmogelijk niet gelijk aan de grootte van het bestand.

Een volledig correcte, standaard-compatibele en draagbare oplossing kan worden geconstrueerd met behulp van std::ifstream‘s rdbuf()in een std::ostringstreamen van daaruit naar een std::string. Dit kan echter de stringgegevens kopiëren en/of het geheugen nodeloos opnieuw toewijzen.

  • Zijn alle relevante implementaties van standaardbibliotheken slim genoeg om alle onnodige overhead te vermijden?
  • Is er een andere manier om dit te doen?
  • Heb ik een verborgen Boost-functie gemist die al de gewenste functionaliteit biedt?

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

Antwoord 1, autoriteit 100%

Eén manier is om de streambuffer in een aparte geheugenstroom te spoelen en die vervolgens te converteren naar std::string(foutafhandeling weggelaten):

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

Dit is mooi beknopt. Zoals opgemerkt in de vraag, wordt hiermee echter een overbodige kopie gemaakt en helaas is er in principe geen manier om deze kopie te verwijderen.

De enige echte oplossing die overbodige kopieën vermijdt, is om het lezen helaas handmatig in een lus uit te voeren. Aangezien C++ nu gegarandeerde aaneengesloten strings heeft, zou men het volgende kunnen schrijven (≥C++17, inclusief foutafhandeling):

auto read_file(std::string_view path) -> std::string {
    constexpr auto read_size = std::size_t(4096);
    auto stream = std::ifstream(path.data());
    stream.exceptions(std::ios_base::badbit);
    auto out = std::string();
    auto buf = std::string(read_size, '\0');
    while (stream.read(& buf[0], read_size)) {
        out.append(buf, 0, stream.gcount());
    }
    out.append(buf, 0, stream.gcount());
    return out;
}

Antwoord 2, autoriteit 46%

De kortste variant: Live On Coliru

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

Het vereist de header <iterator>.

Er waren enkele rapporten dat deze methode langzamer is dan het vooraf toewijzen van de string en het gebruik van std::istream::read. Op een moderne compiler met ingeschakelde optimalisaties lijkt dit echter niet langer het geval te zijn, hoewel de relatieve prestaties van verschillende methoden sterk afhankelijk lijken te zijn van de compiler.


Antwoord 3, autoriteit 35%

Zie dit antwoordop een soortgelijke vraag.

Voor uw gemak plaats ik de oplossing van CTT opnieuw:

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

Deze oplossing resulteerde in ongeveer 20% snellere uitvoeringstijden dan de andere antwoorden die hier worden gepresenteerd, wanneer het gemiddelde van 100 runs wordt vergeleken met de tekst van Moby Dick (1,3 miljoen). Niet slecht voor een draagbare C++-oplossing, ik zou graag de resultaten zien van het mappen van het bestand 😉


Antwoord 4, autoriteit 21%

Als je C++17 (std::filesystem) hebt, is er ook deze manier (die de bestandsgrootte krijgt via std::filesystem::file_sizein plaats van seekgen tellg):

#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, std::ios::in | std::ios::binary);
    // Obtain the size of the file.
    const auto sz = fs::file_size(path);
    // Create a buffer.
    std::string result(sz, '\0');
    // Read the whole file into the buffer.
    f.read(result.data(), sz);
    return result;
}

Opmerking: mogelijk moet u <experimental/filesystem>en std::experimental::filesystemgebruiken als uw standaardbibliotheek dat niet doet ondersteunt C++17 nog niet volledig. Mogelijk moet u ook result.data()vervangen door &result[0]als het niet-const std::basic_string data.


Antwoord 5, autoriteit 16%

Gebruik

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

of iets heel dichtbij. Ik heb geen stdlib-referentie geopend om mezelf te controleren.

Ja, ik begrijp dat ik de functie slurpniet heb geschreven zoals gevraagd.


Antwoord 6, autoriteit 11%

Ik heb niet genoeg reputatie om rechtstreeks op reacties te reageren met tellg().

Houd er rekening mee dat tellg()-1 kan retourneren bij een fout. Als u het resultaat van tellg()doorgeeft als toewijzingsparameter, moet u eerst het resultaat controleren.

Een voorbeeld van het probleem:

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

In het bovenstaande voorbeeld, als tellg()een fout tegenkomt, wordt -1 geretourneerd. Impliciet casten tussen ondertekend (dwz het resultaat van tellg()) en niet-ondertekend (dwz de arg naar de vector<char>-constructor) zal ertoe leiden dat uw vector ten onrechte een zeergroot aantal bytes. (Waarschijnlijk 4294967295 bytes, of 4 GB.)

Paxos1977’s antwoord aanpassen om rekening te houden met het bovenstaande:

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

Antwoord 7, autoriteit 5%

Deze oplossing voegt foutcontrole toe aan de op rdbuf() gebaseerde methode.

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

Ik voeg dit antwoord toe omdat het toevoegen van foutcontrole aan de oorspronkelijke methode niet zo triviaal is als je zou verwachten. De originele methode gebruikt de invoegoperator van stringstream (str_stream << file_stream.rdbuf()). Het probleem is dat dit de failbit van de stringstream instelt wanneer er geen tekens worden ingevoegd. Dat kan te wijten zijn aan een fout of aan het feit dat het bestand leeg is. Als u op fouten controleert door de failbit te inspecteren, zult u een vals positief resultaat tegenkomen wanneer u een leeg bestand leest. Hoe ondubbelzinnig het legitieme falen om tekens in te voegen en het “falen” om tekens in te voegen ondubbelzinnig maken omdat het bestand leeg is?

Je zou kunnen denken om expliciet te controleren op een leeg bestand, maar dat is meer code en bijbehorende foutcontrole.

Controleren op de foutconditie str_stream.fail() && !str_stream.eof()werkt niet, omdat de invoegbewerking de eofbit niet instelt (op de ostringstream noch op de ifstream).

Dus de oplossing is om de bewerking te wijzigen. In plaats van de invoegoperator van ostringstream (<<), gebruik je de extractie-operator van ifstream (>>), die de eofbit instelt. Controleer vervolgens op de foutconditie file_stream.fail() && !file_stream.eof().

Belangrijk, wanneer file_stream >> str_stream.rdbuf()een legitieme fout tegenkomt, zou het nooit eofbit moeten instellen (volgens mijn begrip van de specificatie). Dat betekent dat de bovenstaande controle voldoende is om legitieme fouten te detecteren.


Antwoord 8, autoriteit 4%

Hier is een versie die de nieuwe bestandssysteembibliotheek gebruikt met redelijk robuuste foutcontrole:

#include <cstdint>
#include <exception>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
namespace fs = std::filesystem;
std::string loadFile(const char *const name);
std::string loadFile(const std::string &name);
std::string loadFile(const char *const name) {
  fs::path filepath(fs::absolute(fs::path(name)));
  std::uintmax_t fsize;
  if (fs::exists(filepath)) {
    fsize = fs::file_size(filepath);
  } else {
    throw(std::invalid_argument("File not found: " + filepath.string()));
  }
  std::ifstream infile;
  infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
  try {
    infile.open(filepath.c_str(), std::ios::in | std::ifstream::binary);
  } catch (...) {
    std::throw_with_nested(std::runtime_error("Can't open input file " + filepath.string()));
  }
  std::string fileStr;
  try {
    fileStr.resize(fsize);
  } catch (...) {
    std::stringstream err;
    err << "Can't resize to " << fsize << " bytes";
    std::throw_with_nested(std::runtime_error(err.str()));
  }
  infile.read(fileStr.data(), fsize);
  infile.close();
  return fileStr;
}
std::string loadFile(const std::string &name) { return loadFile(name.c_str()); };

Antwoord 9, autoriteit 4%

Aangezien dit een veelgebruikt hulpprogramma lijkt, zou mijn benadering zijn om te zoeken naar en de voorkeur te geven aan reeds beschikbare bibliotheken boven handgemaakte oplossingen, vooral als boostbibliotheken al zijn gekoppeld (linkervlaggen -lboost_system -lboost_filesystem) in uw project. Hier (en ook oudere boostversies)biedt boost een load_string_file-hulpprogramma:

#include <iostream>
#include <string>
#include <boost/filesystem/string_file.hpp>
int main() {
    std::string result;
    boost::filesystem::load_string_file("aFileName.xyz", result);
    std::cout << result.size() << std::endl;
}

Als voordeel zoekt deze functie niet naar een heel bestand om de grootte te bepalen, maar gebruikt hij intern stat(). Als een mogelijk verwaarloosbaar nadeel zou men echter gemakkelijk kunnen concluderen bij inspectie van de broncode: de tekenreeks wordt onnodig verkleind met '\0'-tekens die worden herschreven door de inhoud van het bestand.


Antwoord 10, autoriteit 3%

Zoiets zou niet zo erg moeten zijn:

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

Het voordeel hier is dat we eerst de reserve doen, zodat we de string niet hoeven te laten groeien terwijl we dingen inlezen. Het nadeel is dat we het char voor char doen. Een slimmere versie zou de hele leesbuffer kunnen pakken en dan underflow kunnen aanroepen.


Antwoord 11, autoriteit 2%

U kunt de functie ‘std::getline’ gebruiken en ‘eof’ als scheidingsteken opgeven. De resulterende code is echter een beetje onduidelijk:

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

Antwoord 12

#include <string>
#include <sstream>
using namespace std;
string GetStreamAsString(const istream& in)
{
    stringstream out;
    out << in.rdbuf();
    return out.str();
}
string GetFileAsString(static string& filePath)
{
    ifstream stream;
    try
    {
        // Set to throw on failure
        stream.exceptions(fstream::failbit | fstream::badbit);
        stream.open(filePath);
    }
    catch (system_error& error)
    {
        cerr << "Failed to open '" << filePath << "'\n" << error.code().message() << endl;
        return "Open fail";
    }
    return GetStreamAsString(stream);
}

gebruik:

const string logAsString = GetFileAsString(logFilePath);

Antwoord 13

Een bijgewerkte functie die voortbouwt op de oplossing van CTT:

#include <string>
#include <fstream>
#include <limits>
#include <string_view>
std::string readfile(const std::string_view path, bool binaryMode = true)
{
    std::ios::openmode openmode = std::ios::in;
    if(binaryMode)
    {
        openmode |= std::ios::binary;
    }
    std::ifstream ifs(path.data(), openmode);
    ifs.ignore(std::numeric_limits<std::streamsize>::max());
    std::string data(ifs.gcount(), 0);
    ifs.seekg(0);
    ifs.read(data.data(), data.size());
    return data;
}

Er zijn twee belangrijke verschillen:

tellg()geeft niet gegarandeerd de offset in bytes terug sinds het begin van het bestand. In plaats daarvan, zoals Puzomor Kroatië opmerkte, is het meer een token dat kan worden gebruikt binnen de fstream-aanroepen. gcount()geeft echterhet aantal niet-geformatteerde bytes terug dat het laatst is geëxtraheerd. We openen daarom het bestand, extraheren en verwijderen alle inhoud met ignore()om de grootte van het bestand te krijgen, en construeren op basis daarvan de uitvoerreeks.

Ten tweede vermijden we dat we de gegevens van het bestand moeten kopiëren van een std::vector<char>naar een std::stringdoor rechtstreeks naar de string te schrijven .

In termen van prestaties zou dit absoluut de snelste moeten zijn, door vooraf de juiste tekenreeks toe te wijzen en eenmaal read()aan te roepen. Interessant is het gebruik van ignore()en countg()in plaats van ateen tellg()op gcc compileert naar bijna hetzelfde, beetje bij beetje.


Antwoord 14

#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
main(){
    fstream file;
    //Open a file
    file.open("test.txt");
    string copy,temp;
    //While loop to store whole document in copy string
    //Temp reads a complete line
    //Loop stops until temp reads the last line of document
    while(getline(file,temp)){
        //add new line text in copy
        copy+=temp;
        //adds a new line
        copy+="\n";
    }
    //Display whole document
    cout<<copy;
    //close the document
    file.close();
}

Antwoord 15

dit is de functie die ik gebruik, en bij het omgaan met grote bestanden (1GB+) om de een of andere reden is std::ifstream::read() veelsneller dan std::ifstream::rdbuf( ) als je de bestandsgrootte kent, dus het hele “controleer eerst de bestandsgrootte” is eigenlijk een snelheidsoptimalisatie

#include <string>
#include <fstream>
#include <sstream>
std::string file_get_contents(const std::string &$filename)
{
    std::ifstream file($filename, std::ifstream::binary);
    file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    file.seekg(0, std::istream::end);
    const std::streampos ssize = file.tellg();
    if (ssize < 0)
    {
        // can't get size for some reason, fallback to slower "just read everything"
        // because i dont trust that we could seek back/fourth in the original stream,
        // im creating a new stream.
        std::ifstream file($filename, std::ifstream::binary);
        file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        std::ostringstream ss;
        ss << file.rdbuf();
        return ss.str();
    }
    file.seekg(0, std::istream::beg);
    std::string result(size_t(ssize), 0);
    file.read(&result[0], std::streamsize(ssize));
    return result;
}

Antwoord 16

Voor de prestaties heb ik niets sneller gevonden dan de onderstaande code.

std::string readAllText(std::string const &path)
{
    assert(path.c_str() != NULL);
    FILE *stream = fopen(path.c_str(), "r");
    assert(stream != NULL);
    fseek(stream, 0, SEEK_END);
    long stream_size = ftell(stream);
    fseek(stream, 0, SEEK_SET);
    void *buffer = malloc(stream_size);
    fread(buffer, stream_size, 1, stream);
    assert(ferror(stream) == 0);
    fclose(stream);
    std::string text((const char *)buffer, stream_size);
    assert(buffer != NULL);
    free((void *)buffer);
    return text;
}

Antwoord 17

Je kunt de rstgebruiken C++-bibliotheek die ik daarvoor heb ontwikkeld:

#include "rst/files/file_utils.h"
std::filesystem::path path = ...;  // Path to a file.
rst::StatusOr<std::string> content = rst::ReadFile(path);
if (content.err()) {
  // Handle error.
}
std::cout << *content << ", " << content->size() << std::endl;

Antwoord 18

Ik weet dat dit een zeer oude vraag is met een overvloed aan antwoorden, maar geen van hen vermeldt wat ik zou hebben beschouwd als de meest voor de hand liggende manier om dit te doen. Ja, ik weet dat dit C++ is, en het gebruik van libc is slecht en verkeerd of wat dan ook, maar daar gek op. Het gebruik van libc is prima, vooral voor zoiets eenvoudigs als dit.

In wezen: open het bestand, zoek de grootte op (niet noodzakelijk in die volgorde) en lees het.

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>
static constexpr char const filename[] = "foo.bar";
int main(void)
{
    FILE *fp = ::fopen(filename, "rb");
    if (!fp) {
        ::perror("fopen");
        ::exit(1);
    }
    struct stat st;
    if (::fstat(fileno(fp), &st) == (-1)) {
        ::perror("fstat");
        ::exit(1);
    }
    // You could simply allocate a buffer here and use std::string_view, or
    // even allocate a buffer and copy it to a std::string. Creating a
    // std::string and setting its size is simplest, but will pointlessly
    // initialize the buffer to 0. You can't win sometimes.
    std::string str;
    str.reserve(st.st_size + 1U);
    str.resize(st.st_size);
    ::fread(str.data(), 1, st.st_size, fp);
    str[st.st_size] = '\0';
    ::fclose(fp);
}

Dit lijkt niet echt slechter dan sommige van de andere oplossingen, behalve dat ze (in de praktijk) volledig draagbaar zijn. Je zou natuurlijk ook een exception kunnen gooien in plaats van meteen af ​​te sluiten. Het irriteert me serieus dat het formaat van de std::stringaltijd 0 initialiseert, maar het kan niet worden geholpen.

LET OPdat dit alleen gaat werken zoals geschreven voor C++17 en later. Eerdere versies zouden het bewerken van std::string::data()niet toestaan. Als je met een eerdere versie werkt, overweeg dan om std::string_viewte gebruiken of gewoon een onbewerkte buffer te kopiëren.


Antwoord 19

Schrijf nooit in de std::string’s const char * buffer. Nooit! Dit doen is een enorme fout.

Reserveer() ruimte voor de hele string in je std::string, lees stukjes uit je bestand van redelijke grootte in een buffer, en voeg het toe (). Hoe groot de chunks moeten zijn, hangt af van de grootte van uw invoerbestand. Ik ben er vrij zeker van dat alle andere draagbare en STL-compatibele mechanismen hetzelfde zullen doen (maar er misschien mooier uitzien).


Antwoord 20

Ik weet dat ik te laat op het feest ben, maar nu (2021) op mijn computer is dit de snelste implementatie die ik heb getest:

#include <fstream>
#include <string>
bool fileRead( std::string &contents, const std::string &path ) {
    contents.clear();
    if( path.empty()) {
        return false;
    }
    std::ifstream stream( path );
    if( !stream ) {
        return false;
    }
    stream >> contents;
    return true;
}

Other episodes