274 votes

Comment lire un fichier entier dans une chaîne std::string en C++ ?

Comment lire un fichier dans un std::string c'est-à-dire lire tout le fichier en une seule fois ?

Le mode texte ou binaire doit être spécifié par l'appelant. La solution doit être conforme aux normes, portable et efficace. Elle ne doit pas copier inutilement les données de la chaîne et doit éviter les réaffectations de mémoire lors de la lecture de la chaîne.

Une façon d'y parvenir serait de statuer sur la taille des fichiers, de redimensionner les std::string y fread() dans le std::string 's const_cast<char*>() 'ed data() . Cela nécessite le std::string Les données de l'utilisateur sont contiguës, ce qui n'est pas requis par la norme, mais semble être le cas pour toutes les implémentations connues. Pire encore, si le fichier est lu en mode texte, la fonction std::string La taille du fichier peut ne pas être égale à la taille du fichier.

Une solution tout à fait correcte, conforme aux normes et portable pourrait être construite en utilisant std::ifstream 's rdbuf() en un std::ostringstream et de là, dans un std::string . Cependant, cela pourrait copier les données de la chaîne et/ou réallouer inutilement de la mémoire.

  • Toutes les implémentations pertinentes de la bibliothèque standard sont-elles suffisamment intelligentes pour éviter toute surcharge inutile ?
  • Y a-t-il un autre moyen de le faire ?
  • Ai-je manqué une fonction cachée de Boost qui fournit déjà la fonctionnalité souhaitée ?

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

175voto

Konrad Rudolph Points 231505

Une façon de procéder est de vider le tampon de flux dans un flux de mémoire séparé, puis de le convertir en std::string (traitement des erreurs omis) :

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

C'est joliment concis. Cependant, comme indiqué dans la question, il s'agit d'une copie redondante et, malheureusement, il n'y a fondamentalement aucun moyen d'éluder cette copie.

La seule véritable solution permettant d'éviter les copies redondantes consiste à effectuer la lecture manuellement dans une boucle, malheureusement. Puisque C++ a maintenant des chaînes contiguës garanties, on pourrait écrire ce qui suit (≥C++17, gestion des erreurs incluse) :

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

93voto

Konrad Rudolph Points 231505

La variante la plus courte : <a href="http://coliru.stacked-crooked.com/a/ab16e6dd34c38b92" rel="noreferrer">Live On Coliru</a>

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

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

Certains rapports indiquent que cette méthode est plus lente que la pré-affectation de la chaîne de caractères et l'utilisation de la fonction std::istream::read . Cependant, sur un compilateur moderne avec des optimisations activées, cela ne semble plus être le cas, bien que les performances relatives des différentes méthodes semblent dépendre fortement du compilateur.

57voto

paxos1977 Points 25088

Ver cette réponse sur une question similaire.

Pour vous faciliter la tâche, je republie 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 donné des temps d'exécution environ 20% plus rapides que les autres réponses présentées ici, en prenant la moyenne de 100 exécutions contre le texte de Moby Dick (1.3M). Pas mal pour une solution C++ portable, j'aimerais voir les résultats de mmap'ing du fichier ;)

52voto

Gabriel Majeri Points 485

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

Note : vous devrez peut-être utiliser <experimental/filesystem> y std::experimental::filesystem si votre bibliothèque standard ne supporte pas encore complètement C++17. Vous devrez peut-être aussi remplacer result.data() con &result[0] s'il ne supporte pas données non-const std::basic_string .

28voto

Ben Collins Points 11318

Utilisez

#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 vérifier moi-même.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X