web-dev-qa-db-fra.com

Quel est le meilleur moyen de lire un fichier entier dans un std :: string en C ++?

Comment lire un fichier dans un std::string, C’est-à-dire lire tout le fichier à la fois?

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

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

Une solution entièrement correcte, conforme aux normes et portable pourrait être construite en utilisant la fonction rdbuf() de std::ifstream Dans un std::ostringstream, Puis dans un std::string. Toutefois, cela pourrait copier les données de chaîne et/ou réallouer inutilement de la mémoire. Toutes les implémentations de bibliothèques standard pertinentes sont-elles suffisamment intelligentes pour éviter toute surcharge inutile? Y a-t-il une autre façon de le faire? Ai-je oublié une fonction Boost cachée offrant déjà la fonctionnalité souhaitée?

S'il vous plaît montrer votre suggestion comment le mettre en œuvre.

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

en tenant compte de la discussion ci-dessus.

149
wilbur_m

Et le plus rapide (à ma connaissance, en actualisant les fichiers mappés en mémoire):

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 flux de chaîne. (Le static_cast est nécessaire puisque operator << retourne une vieille plaine ostream& mais nous savons qu’en réalité c’est un stringstream& donc le casting est en sécurité.)

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 encore une fois en une seule ligne:

std::string Slurp(std::ifstream& in) {
    return static_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str();
}
125
Konrad Rudolph

Voir cette réponse sur une question similaire.

Pour votre commodité, 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 permis d’obtenir des temps d’exécution environ 20% plus rapides que les autres réponses présentées ici, en prenant la moyenne de 100 analyses par rapport au texte de Moby Dick (1,3M). Pas mal pour une solution C++ portable, j'aimerais voir les résultats de mmap 'le fichier;)

48
paxos1977

La variante la plus courte: Live On Colir

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

Il nécessite l'en-tête <iterator>.

Selon certains rapports, 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 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.

43
Konrad Rudolph

Utilisation

#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 une double vérification.

Oui, je comprends que je n’ai pas écrit la fonction Slurp comme demandé.

16
Ben Collins

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

Veuillez noter que tellg() peut renvoyer -1 en cas d'erreur. Si vous transmettez le résultat de tellg() en tant que paramètre d'allocation, vous devez d'abord vérifier le résultat.

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 retournera -1. Le transtypage implicite entre signé (c'est-à-dire le résultat de tellg()) et non signé (c'est-à-dire l'argument du constructeur vector<char>) Aura pour conséquence que votre vecteur attribuera à tort très grand nombre d'octets. (Probablement 4294967295 octets ou 4 Go.)

Modifier la réponse de paxos1977 pour tenir compte de ce qui précède:

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);
}
12
Rick Ramstetter

Si vous avez C++ 17 (std :: système de fichiers), il existe également une méthode (qui obtient la taille du fichier via 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;
}

Remarque : vous devrez peut-être utiliser <experimental/filesystem> Et std::experimental::filesystem Si votre bibliothèque standard ne supporte pas encore complètement C + +17. Vous devrez peut-être aussi remplacer result.data() par &result[0] Si elle ne prend pas en charge données non const std :: basic_string .

5
Gabriel M

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 d'origine n'est pas aussi simple que prévu. La méthode d'origine utilise l'opérateur d'insertion de stringstream (str_stream << file_stream.rdbuf()). Le problème est que cela définit le failbit du stringstream quand aucun caractère n'est inséré. Cela peut être dû à une erreur ou à un fichier vide. Si vous vérifiez les échecs en inspectant le failbit, vous rencontrerez un faux positif lorsque vous lirez un fichier vide. Comment désambiguïser l'échec légitime d'insérer des caractères et "échec" d'insérer des caractères car le fichier est vide?

Vous pourriez penser à rechercher explicitement un fichier vide, mais cela nécessite davantage de code et une vérification d'erreur associée.

La vérification de la condition d'échec str_stream.fail() && !str_stream.eof() ne fonctionne pas, car l'opération d'insertion ne définit pas l'eofbit (sur le flux ostring ni sur le flux if).

La solution consiste donc à modifier l'opération. Au lieu d'utiliser l'opérateur d'insertion d'ostringstream (<<), utilisez l'opérateur d'extraction de ifstream (>>), qui définit l'eofbit. Puis vérifiez la condition d'échec file_stream.fail() && !file_stream.eof().

Il est important de noter que lorsque file_stream >> str_stream.rdbuf() rencontre un échec légitime, il ne devrait jamais définir eofbit (selon ma compréhension de la spécification). Cela signifie que la vérification ci-dessus est suffisante pour détecter les défaillances légitimes.

3
tgnottingham

N'écrivez jamais dans const * buffer de std :: string. Plus jamais! Cela est une grave erreur.

Réservez () de l’espace pour toute la chaîne dans votre std :: string, lisez des morceaux de votre fichier de taille raisonnable dans un tampon et ajoutez-le (). 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 portables et compatibles STL feront de même (mais peuvent paraître plus jolis).

3
Thorsten79

Quelque chose comme ça ne devrait pas être trop grave:

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 d’abord la réserve pour ne pas avoir à augmenter la longueur de la chaîne. L’inconvénient est que nous le faisons caractère par caractère. Une version plus intelligente pourrait récupérer l’ensemble du fichier lu, puis appeler un sous-remplissage.

3
Matt Price

Vous pouvez utiliser la fonction 'std :: getline' et spécifier 'eof' comme délimiteur. Le code résultant est cependant 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
Martin Cote