Come faccio a leggere un intero file in una stringa std :: string in C ++?


178

Come faccio a leggere un file in un std::string, cioè, leggere l'intero file contemporaneamente?

La modalità testo o binaria deve essere specificata dal chiamante. La soluzione dovrebbe essere conforme agli standard, portatile ed efficiente. Non dovrebbe inutilmente copiare i dati della stringa e dovrebbe evitare riallocazioni della memoria durante la lettura della stringa.

Un modo per farlo sarebbe quello di stat la dimensione del file, ridimensionare std::stringe fread()nella std::string's const_cast<char*>()' ed data(). Ciò richiede che std::stringi dati siano contigui, il che non è richiesto dalla norma, ma sembra essere il caso di tutte le implementazioni conosciute. Quel che è peggio, se il file viene letto in modalità testo, la std::stringdimensione del file potrebbe non corrispondere alla dimensione del file.

Una soluzione completamente corretta, conforme agli standard e portatile potrebbe essere costruita usando std::ifstream's rdbuf()in a std::ostringstreame da li in a std::string. Tuttavia, ciò potrebbe copiare i dati della stringa e / o riallocare inutilmente la memoria.

  • Tutte le implementazioni delle librerie standard pertinenti sono abbastanza intelligenti da evitare qualsiasi sovraccarico inutile?
  • C'è un altro modo per farlo?
  • Ho perso alcune funzioni Boost nascoste che forniscono già la funzionalità desiderata?


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

Tieni presente che hai ancora alcune cose non specificate. Ad esempio, qual è la codifica dei caratteri del file? Tenterai di rilevare automaticamente (che funziona solo in alcuni casi specifici)? Onorerai ad esempio le intestazioni XML che ti dicono la codifica del file? Inoltre non esiste una "modalità testo" o "modalità binaria" - stai pensando all'FTP?
Jason Cohen,

La modalità testo e binaria sono hack specifici di MSDOS e Windows che cercano di aggirare il fatto che i newline sono rappresentati da due caratteri in Windows (CR / LF). In modalità testo, sono trattati come un carattere ('\ n').
Ferruccio,

1
Sebbene non (esattamente) un duplicato esattamente, questo è strettamente correlato a: come pre-allocare memoria per un oggetto std :: string? (che, contrariamente alla precedente dichiarazione di Konrad, includeva il codice per farlo, leggendo il file direttamente nella destinazione, senza fare una copia aggiuntiva).
Jerry Coffin,

1
"lo standard non è richiesto contiguo" - sì, lo è, in maniera rotonda. Non appena si utilizza op [] sulla stringa, deve essere riunito in un buffer scrivibile contiguo, quindi è sicuro scrivere su & str [0] se .resize () è abbastanza grande per primo. E in C ++ 11, la stringa è semplicemente sempre contigua.
Tino Didriksen,

2
Link correlato: Come leggere un file in C ++? - benchmark e discute i vari approcci. E sì, rdbuf(quello nella risposta accettata) non è il più veloce, lo readè.
legends2k,

Risposte:


138

Un modo è svuotare il buffer di flusso in un flusso di memoria separato, quindi convertirlo in std::string:

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

Questo è ben conciso. Tuttavia, come notato nella domanda, ciò esegue una copia ridondante e sfortunatamente non c'è fondamentalmente modo di eludere questa copia.

L'unica vera soluzione che evita le copie ridondanti è purtroppo fare la lettura manualmente in un ciclo. Poiché ora C ++ ha garantito stringhe contigue, si potrebbe scrivere quanto segue (≥C ++ 14):

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

20
Qual è il punto di renderlo un oneliner? Opterei sempre per un codice leggibile. Come appassionato di VB.Net autodidatta (IIRC) penso che dovresti capire il sentimento?
visto il

5
@sehe: Mi aspetterei che qualsiasi programmatore C ++ a metà competenza sia in grado di comprendere prontamente quel one-liner. È piuttosto docile rispetto ad altre cose in circolazione.
DevSolar,

43
@DevSolar Bene, la versione più leggibile è ~ 30% più corta, manca di un cast ed è altrimenti equivalente. La mia domanda quindi si pone: "Qual è il punto di renderlo un oneliner?"
Visto il

13
nota: questo metodo legge il file nel buffer dello stringstream, quindi copia l'intero buffer nel file string. Cioè richiede il doppio della memoria rispetto ad alcune delle altre opzioni. (Non è possibile spostare il buffer). Per un file di grandi dimensioni sarebbe una penalità significativa, forse anche causando un errore di allocazione.
MM

9
@DanNissenbaum Stai confondendo qualcosa. La concisione è davvero importante nella programmazione, ma il modo corretto per raggiungerlo è scomporre il problema in parti e incapsularle in unità indipendenti (funzioni, classi, ecc.). L'aggiunta di funzioni non toglie la concisione; al contrario.
Konrad Rudolph,

52

Vedi questa risposta su una domanda simile.

Per tua comodità, sto ripubblicando la soluzione CTT:

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

Questa soluzione ha comportato tempi di esecuzione più rapidi di circa il 20% rispetto alle altre risposte qui presentate, prendendo la media di 100 corse rispetto al testo di Moby Dick (1.3M). Non male per una soluzione C ++ portatile, vorrei vedere i risultati di mmap'ing del file;)


3
correlati: confronto delle prestazioni temporali di vari metodi: lettura di un intero file contemporaneamente in C ++
jfs

12
Fino ad oggi, non ho mai visto tellg () riportare risultati non di dimensioni file. Mi ci sono volute ore per trovare l'origine del bug. Non utilizzare tellg () per ottenere le dimensioni del file. stackoverflow.com/questions/22984956/...
Puzomor Croazia

non dovresti chiamare ifs.seekg(0, ios::end)prima tellg? subito dopo l'apertura di un puntatore di lettura file è all'inizio e quindi tellgrestituisce zero
Andriy Tylychko

1
Inoltre è necessario verificare la presenza di file vuoti, come si dereference nullptrda&bytes[0]
Andriy Tylychko

ok, mi sono perso ios::ate, quindi penso che una versione con esplicito spostamento verso la fine sarebbe più leggibile
Andriy Tylychko

50

La variante più corta: Live On Coliru

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

Richiede l'intestazione <iterator>.

Ci sono stati alcuni rapporti secondo cui questo metodo è più lento del preallocazione della stringa e dell'utilizzo std::istream::read. Tuttavia, su un moderno compilatore con ottimizzazioni abilitate, ciò non sembra più essere il caso, sebbene le prestazioni relative di vari metodi sembrano essere altamente dipendenti dal compilatore.


7
Potresti scadere su questa risposta. Quanto è efficace, legge un file alla volta, per preallocare la memoria di agitazione?
Martin Beckett,

@MM Il modo in cui ho letto questo confronto, questo metodo è più lento del puro metodo di lettura C ++ in un buffer preallocato.
Konrad Rudolph,

Hai ragione, è un caso che il titolo sia sotto l'esempio di codice, piuttosto che sopra di esso :)
MM

@juzzlin C ++ non funziona così. Non richiedere un'intestazione in un ambiente specifico non è una buona ragione per non includerla.
LF

Questo metodo attiverà la riallocazione della memoria per molte volte?
coin cheung l'

22

Uso

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

o qualcosa di molto vicino. Non ho un riferimento stdlib aperto per ricontrollare me stesso.

Sì, capisco di non aver scritto la slurpfunzione come richiesto.


Sembra carino, ma non compilabile. Le modifiche per farlo compilare lo riducono ad altre risposte in questa pagina. ideone.com/EyhfWm
JDiMatteo

5
Perché il ciclo while?
Zitrax,

Concordato. Quando viene operator>>letto in a std::basic_streambuf, consumerà (ciò che resta di) il flusso di input, quindi il ciclo non è necessario.
Remy Lebeau,

15

Se hai C ++ 17 (std :: filesystem), c'è anche questo modo (che ottiene la dimensione del file attraverso std::filesystem::file_sizeinvece di seekge 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;
}

Nota : potrebbe essere necessario utilizzare <experimental/filesystem>e std::experimental::filesystemse la libreria standard non supporta ancora completamente C ++ 17. Potrebbe anche essere necessario sostituirlo result.data()con &result[0]se non supporta i dati non const std :: basic_string .


1
Ciò può causare comportamenti indefiniti; l'apertura del file in modalità testo produce un flusso diverso rispetto al file del disco su alcuni sistemi operativi.
MM

1
Originariamente sviluppato in boost::filesystemmodo da poter usare anche boost se non hai c ++ 17
Gerhard Burger

2
Aprire un file con un'API e ottenere le sue dimensioni con un altro sembra richiedere incoerenza e condizioni di competizione.
Arthur Tacca,

14

Non ho abbastanza reputazione per commentare direttamente le risposte usando tellg().

Si noti che tellg()può restituire -1 in caso di errore. Se si passa il risultato tellg()come parametro di allocazione, è necessario verificare innanzitutto il buonsenso.

Un esempio del problema:

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

Nell'esempio precedente, se si tellg()verifica un errore, verrà restituito -1. Il cast implicito tra firmato (cioè il risultato di tellg()) e non firmato (cioè l'arg per il vector<char>costruttore) comporterà un vettore che alloca erroneamente un numero molto elevato di byte. (Probabilmente 4294967295 byte o 4 GB.)

Modifica della risposta di paxos1977 per tenere conto di quanto sopra:

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

5

Questa soluzione aggiunge il controllo degli errori al metodo basato su rdbuf ().

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

Sto aggiungendo questa risposta perché aggiungere il controllo degli errori al metodo originale non è banale come ci si aspetterebbe. Il metodo originale utilizza l'operatore di inserimento di stringstream ( str_stream << file_stream.rdbuf()). Il problema è che questo imposta il failbit dello stringstream quando non vengono inseriti caratteri. Ciò può essere dovuto a un errore o al fatto che il file è vuoto. Se si verificano errori verificando il failbit, si verificherà un falso positivo quando si legge un file vuoto. Come si fa a non chiarire il legittimo fallimento dell'inserimento di caratteri e il "fallimento" nell'inserimento di caratteri perché il file è vuoto?

Potresti pensare di verificare esplicitamente la presenza di un file vuoto, ma si tratta di più codice e controllo degli errori associati.

Il controllo della condizione di errore str_stream.fail() && !str_stream.eof()non funziona, poiché l'operazione di inserimento non imposta l'eofbit (su ostringstream né ifstream).

Quindi, la soluzione è cambiare l'operazione. Invece di utilizzare l'operatore di inserimento ostringstream (<<), utilizzare l'operatore di estrazione ifstream (>>), che imposta l'eofbit. Quindi verificare la condizione di errore file_stream.fail() && !file_stream.eof().

È importante sottolineare che, quando si file_stream >> str_stream.rdbuf()verifica un errore legittimo, non dovrebbe mai impostare eofbit (secondo la mia comprensione delle specifiche). Ciò significa che il controllo di cui sopra è sufficiente per rilevare errori legittimi.


3

Qualcosa del genere non dovrebbe essere così male:

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

Il vantaggio qui è che facciamo prima la riserva, quindi non dovremo far crescere la stringa mentre leggiamo le cose. Lo svantaggio è che lo facciamo char con char. Una versione più intelligente potrebbe catturare l'intero buf di lettura e quindi chiamare underflow.


1
È necessario verificare la versione di questo codice che utilizza std :: vector per la lettura iniziale anziché una stringa. Molto molto più veloce.
paxos1977,

3

Ecco una versione che utilizza la nuova libreria di filesystem con un controllo degli errori ragionevolmente solido:

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

infile.openpuò anche accettare std::stringsenza convertire con.c_str()
Matt Eding il

filepathnon è un std::string, è un std::filesystem::path. Si scopre che std::ifstream::openpuò accettare anche uno di quelli.
David G

@DavidG, std::filesystem::pathè implicitamente convertibile instd::string
Jeffrey Cash il

Secondo cppreference.com, la ::openfunzione membro su std::ifstreamche accetta std::filesystem::pathfunziona come se il ::c_str()metodo fosse chiamato sul percorso. Il sottostante ::value_typedei percorsi è charin POSIX.
David G,

2

È possibile utilizzare la funzione 'std :: getline' e specificare 'eof' come delimitatore. Il codice risultante è un po 'oscuro però:

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

5
Ho appena provato questo, sembra essere molto più lento rispetto a ottenere la dimensione del file e chiamare la lettura per l'intera dimensione del file in un buffer. Nell'ordine di 12 volte più lento.
David,

Funzionerà solo se non ci sono caratteri "eof" (es. 0x00, 0xff, ...) nel tuo file. Se ci sono, leggerai solo una parte del file.
Olaf Dietsche,

2

Non scrivere mai nel buffer const char * std :: string. Mai e poi mai! Farlo è un grosso errore.

Prenota spazio () per l'intera stringa nella tua std :: string, leggi i blocchi dal tuo file di dimensioni ragionevoli in un buffer e aggiungi (). La dimensione dei blocchi dipende dalla dimensione del file di input. Sono abbastanza sicuro che tutti gli altri meccanismi portatili e conformi a STL faranno lo stesso (ma potrebbero sembrare più carini).


5
Dal C ++ 11 è garantito che sia OK scrivere direttamente nel std::stringbuffer; e credo che abbia funzionato correttamente su tutte le implementazioni reali prima di quello
MM

1
Dal C ++ 17 abbiamo anche un std::string::data()metodo non const per modificare direttamente il buffer di stringa senza ricorrere a trucchi come &str[0].
zett42,

D'accordo con @ zett42 questa risposta è in realtà errata
jeremyong

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

utilizzo:

const string logAsString = GetFileAsString(logFilePath);

0

Una funzione aggiornata che si basa sulla soluzione 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;
}

Ci sono due differenze importanti:

tellg()non è garantito per restituire l'offset in byte dall'inizio del file. Invece, come ha sottolineato Puzomor Croazia, è più un token che può essere utilizzato all'interno delle chiamate fstream. gcount()tuttavia fa restituire la quantità di byte non formattati ultimo estratto. Quindi apriamo il file, estraiamo e scartiamo tutto il suo contenuto ignore()per ottenere la dimensione del file e costruiamo la stringa di output in base a quello.

In secondo luogo, evitiamo di dover copiare i dati del file da a std::vector<char>a a std::stringscrivendo direttamente nella stringa.

In termini di prestazioni, questo dovrebbe essere il più veloce in assoluto, allocando in anticipo la stringa di dimensioni appropriate e chiamando read()una volta. Come fatto interessante, usare ignore()e countg()invece di atee tellg()su gcc si compila quasi alla stessa cosa , poco a poco.


1
Questo codice non funziona, sto ottenendo una stringa vuota. Penso che tu volessi ifs.seekg(0)invece di ifs.clear()(allora funziona).
Xeverous, il

-1
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
main(){
    fstream file;
    file.open("test.txt");
    string copy,temp;
    while(getline(file,temp)){
        copy+=temp;
        copy+="\n";
    }
    cout<<copy;
    file.close();
}

1
Aggiungi la descrizione
Peter,

si prega di visitare e verificare come rispondere a una domanda .
Yunus Temurlenk,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.