Quelle est la meilleure façon de lire un fichier entier dans une chaîne std::string en C++?

Comment puis-je lire un fichier dans un std::string , c'est-à-dire lire l'ensemble du fichier à la fois?

le mode texte ou binaire doit être spécifié par l'appelant. La solution doit être conforme à la norme, portable et efficace. Il ne devrait pas copier inutilement les données de la chaîne, et il devrait éviter les réallocations de mémoire pendant la lecture de la chaîne.

une façon de faire cela serait de stater le filesize, redimensionner le std::string et fread() dans le std::string 's const_cast<char*>() 'ed data() . Cela nécessite que les données du std::string soient contiguës, ce qui n'est pas requis par la norme, mais cela semble être le cas pour toutes les implémentations connues. Ce qui est pire, si le fichier est lu en mode texte, la taille du std::string peut ne pas être égale à la taille du fichier.

une solution parfaitement correcte, conforme à la norme et portable pourrait être construite en utilisant std::ifstream 's rdbuf() dans un std::ostringstream et à partir de là dans un std::string . Cependant, cela pourrait copier les données de chaîne et/ou inutilement réattribuer la mémoire. Toutes les implémentations de bibliothèques standard pertinentes sont-elles suffisamment intelligentes pour éviter toute surcharge inutile? Est-il une autre façon de le faire? Ai-je manqué une fonction de Boost cachée qui fournit déjà la fonctionnalité désirée?

s'il vous plaît montrer votre suggestion comment la mettre en œuvre.

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

compte tenu de la discussion ci-dessus.

128
demandé sur Peter Mortensen 2008-09-22 20:48:02

11 réponses

et le plus rapide (que je connais, en excluant les fichiers mémoire-mapped):

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

cela nécessite l'en-tête supplémentaire <sstream> pour le string stream. (Le static_cast est nécessaire car operator << renvoie un vieux ostream& mais nous savons qu'en réalité c'est un stringstream& donc le moulage est sûr.)

divisé en plusieurs lignes, en déplaçant le temporaire dans une variable, nous obtenons un code plus lisible:

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

Ou, une fois de plus en une seule ligne:

std::string slurp(std::ifstream& in) {
    return static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str();
}
105
répondu Konrad Rudolph 2018-05-29 08:31:49

Voir cette réponse sur une question similaire.

pour votre commodité, je reprends la solution de 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);
}

Cette solution a entraîné des délais d'exécution d'environ 20% plus rapides que les autres réponses présentées ici, en prenant la moyenne de 100 passages contre le texte de Moby Dick (1,3 M). Pas mal pour un portable C++ solution, je voudrais voir les résultats de mmap avec le fichier ;)

44
répondu paxos1977 2017-06-29 23:00:25

la variante la plus courte: en direct sur Coliru

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

Il faut l'en-tête <iterator> .

il y a eu des rapports que cette méthode est plus lente que la préallocation de la chaîne et l'utilisation de std::istream::read . Cependant, sur un compilateur moderne avec des optimisations a permis cela ne semble plus être le cas, bien que la performance relative de diverses méthodes semble à être fortement dépendant du compilateur.

30
répondu Konrad Rudolph 2016-11-23 11:03:11

Utiliser

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

ou quelque chose de très proche. Je n'ai pas de référence stdlib ouverte pour me revérifier.

Oui, je comprends que je n'ai pas écrit la fonction slurp comme demandé.

13
répondu Ben Collins 2017-03-04 05:14:39

Je n'ai pas assez de réputation pour commenter directement les réponses en utilisant tellg() .

s'il vous Plaît être conscient que tellg() peut renvoyer -1 en cas d'erreur. Si vous passez le résultat de tellg() comme paramètre d'allocation, vous devriez vérifier le résultat en premier.

un exemple du problème:

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

Dans l'exemple ci-dessus, si tellg() rencontre une erreur, il renvoie -1. La conversion implicite entre signed (c'est-à-dire le résultat de tellg() ) et unsigned (c'est-à-dire l'arg du constructeur vector<char> ) résultera en un vecteur attribuant par erreur un très grand nombre d'octets. (Probablement 4294967295 octets, ou 4 Go.)

Modification paxos1977 la réponse de compte pour le dessus:

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);
}
8
répondu Rick Ramstetter 2017-03-25 03:38:40

ne Jamais écrire dans le std::string const char * buffer. Jamais, jamais, jamais! Cela est une énorme erreur.

(Réserve) de l'espace pour l'ensemble de la chaîne dans votre std::string, lire des morceaux à partir de votre fichier de taille raisonnable dans un buffer, et append (). La taille des morceaux dépend de la taille de votre fichier d'entrée. Je suis à peu près sûr que tous les autres mécanismes portatifs et conformes aux LST feront de même (mais peuvent sembler plus joli).

6
répondu Thorsten79 2017-03-04 05:15:36

quelque chose comme ça ne devrait pas être trop mal:

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

l'avantage ici est que nous faisons la réserve en premier donc nous n'aurons pas à faire pousser la corde pendant que nous lisons les choses. L'inconvénient, c'est que nous le faisons char par char. Une version plus intelligente pourrait saisir l'ensemble Lire buf et ensuite appeler underflow.

3
répondu Matt Price 2008-09-22 17:14:24

si vous avez C++17 (std:: filesystem), il y a aussi cette façon (qui obtient la taille du fichier par std::filesystem::file_size au lieu de seekg et 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 };

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

Note : vous pouvez avoir besoin d'utiliser <experimental/filesystem> et std::experimental::filesystem si votre bibliothèque standard ne supporte pas encore entièrement C++17. Vous pourriez aussi avoir besoin de remplacer result.data() par &result[0] s'il ne supporte pas non-const std:: basic_string data .

3
répondu Gabriel M 2016-12-01 06:00:14

vous pouvez utiliser la fonction 'std::getline', et spécifier 'eof' comme délimiteur. Le code résultant est un peu obscur:

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
répondu Martin Cote 2008-09-22 17:16:23

cette solution ajoute la vérification des erreurs à la méthode basée sur 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();
}

j'ajoute cette réponse parce que l'ajout de la vérification des erreurs à la méthode originale n'est pas aussi trivial que vous l'espériez. La méthode originale utilise l'opérateur d'insertion de stringstream ( str_stream << file_stream.rdbuf() ). Le problème est que cela permet de définir le failbit du stringstream quand aucun caractère n'est inséré. Cela peut être dû à une erreur ou au fait que le fichier est vide. Si vous vérifiez les échecs par en inspectant la faille, vous rencontrerez un faux positif lorsque vous lirez un fichier vide. Comment désambiguer un échec légitime à insérer des caractères et un" échec " à insérer des caractères parce que le fichier est vide?

Vous pourriez penser explicitement vérifier pour un fichier vide, mais c'est plus de code de vérification d'erreur.

vérification de la condition de défaillance str_stream.fail() && !str_stream.eof() ne fonctionne pas, parce que l'opération d'insertion ne eofbit (sur l'ostringstream ou l'ifstream).

donc, la solution est de changer l'opération. Au lieu d'utiliser l'opérateur d'insertion d'ostringstream (<<), utilisez l'opérateur d'extraction d'ifstream (>>), qui règle l'eofbit. Vérifiez ensuite la condition de défaillance file_stream.fail() && !file_stream.eof() .

fait important, quand file_stream >> str_stream.rdbuf() rencontre un échec légitime, il ne devrait jamais mettre eofbit (selon ma compréhension de la spécification). Cela signifie que le ci-dessus le contrôle est suffisant pour détecter les défaillances légitimes.

2
répondu tgnottingham 2017-03-26 10:15:05

et si vous découpez un fichier 11K, alors vous devez le faire en plusieurs morceaux, donc vous devez utiliser quelque chose comme std::vector pour le découper en gros morceaux de cordes.

0
répondu Gavriel Feria 2013-06-29 23:18:39