Comment puis-je lire un fichier dans un std::string
, c'est-à-dire lire le fichier entier à 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 doit pas copier inutilement les données de la chaîne et il doit éviter les réallocations de mémoire lors de la lecture de la chaîne.
Une façon de faire serait de statuer la taille du fichier, de redimensionner le std::string
et fread()
dans le std::string
's const_cast<char*>()
' ed data()
. Cela nécessite que les std::string
données s soient contiguës, ce qui n'est pas requis par la norme, mais cela semble être le cas pour toutes les implémentations connues. Pire encore, si le fichier est lu en mode texte, la std::string
taille du fichier peut ne pas être égale à la taille du fichier.
Des solutions entièrement correctes, conformes aux normes et portables pourraient être construites en utilisant std::ifstream
des rdbuf()
dans un std::ostringstream
et de là dans un std::string
. Cependant, 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 manqué une fonction Boost cachée qui fournit déjà la fonctionnalité souhaitée?
void slurp(std::string& data, bool is_binary)
rdbuf
(celui de la réponse acceptée) n'est pas le plus rapideread
.Réponses:
Une façon consiste à vider le tampon de flux dans un flux de mémoire séparé, puis à le convertir en
std::string
:C'est très concis. Cependant, comme indiqué dans la question, cela effectue une copie redondante et, malheureusement, il n'y a fondamentalement aucun moyen d'éliminer cette copie.
La seule vraie solution qui évite les copies redondantes est de faire la lecture manuellement en boucle, malheureusement. Puisque C ++ a maintenant garanti des chaînes contiguës, on pourrait écrire ce qui suit (≥C ++ 14):
la source
string
. C'est à dire nécessitant deux fois plus de mémoire que certaines des autres options. (Il n'y a aucun moyen de déplacer le tampon). Pour un fichier volumineux, ce serait une pénalité importante, pouvant même entraîner un échec d'allocation.Voir cette réponse sur une question similaire.
Pour votre commodité, je republie la solution de CTT:
Cette solution a entraîné des temps d'exécution environ 20% plus rapides que les autres réponses présentées ici, en prenant la moyenne de 100 courses contre le texte de Moby Dick (1,3M). Pas mal pour une solution C ++ portable, j'aimerais voir les résultats de la mmap'ing du fichier;)
la source
ifs.seekg(0, ios::end)
avanttellg
? juste après l'ouverture d'un fichier, le pointeur de lecture est au début ettellg
renvoie donc zéronullptr
par&bytes[0]
ios::ate
, donc je pense qu'une version avec déplacement explicite vers la fin serait plus lisibleLa variante la plus courte: Live On Coliru
Il nécessite l'en-tête
<iterator>
.Certains rapports indiquent 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 activées, cela ne semble plus être le cas, bien que les performances relatives de diverses méthodes semblent dépendre fortement du compilateur.la source
Utilisation
ou quelque chose de très proche. Je n'ai pas de référence stdlib ouverte pour me vérifier moi-même.
Oui, je comprends que je n'ai pas écrit la
slurp
fonction comme demandé.la source
operator>>
lit dans astd::basic_streambuf
, il consommera (ce qui reste de) le flux d'entrée, donc la boucle n'est pas nécessaire.Si vous avez C ++ 17 (std :: filesystem), il existe également cette méthode (qui récupère la taille du fichier au
std::filesystem::file_size
lieu deseekg
ettellg
):Remarque : vous devrez peut-être utiliser
<experimental/filesystem>
etstd::experimental::filesystem
si votre bibliothèque standard ne prend pas encore entièrement en charge C ++ 17. Vous devrez peut-être également le remplacerresult.data()
par&result[0]
s'il ne prend pas en charge les données std :: basic_string non const .la source
boost::filesystem
que vous puissiez également utiliser boost si vous n'avez pas de c ++ 17Je n'ai pas assez de réputation pour commenter directement les réponses utilisant
tellg()
.Veuillez noter que
tellg()
peut renvoyer -1 en cas d'erreur. Si vous transmettez le résultat de entellg()
tant que paramètre d'allocation, vous devez d'abord vérifier le résultat.Un exemple du problème:
Dans l'exemple ci-dessus, si
tellg()
une erreur se produit, il renverra -1. Le transtypage implicite entre signé (c'est-à-dire le résultat detellg()
) et non signé (c'est-à-dire l'argument duvector<char>
constructeur) se traduira par une allocation erronée par votre vecteur d'un très grand nombre d'octets. (Probablement 4294967295 octets, soit 4 Go.)Modification de la réponse de paxos1977 pour tenir compte de ce qui précède:
la source
Cette solution ajoute la vérification des erreurs à la méthode basée sur rdbuf ().
J'ajoute cette réponse car l'ajout de la vérification des erreurs à la méthode d'origine n'est pas aussi trivial 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 flux de chaînes lorsqu'aucun caractère n'est inséré. Cela peut être dû à une erreur ou au fait que le fichier est vide. Si vous recherchez des échecs en inspectant le failbit, vous rencontrerez un faux positif lorsque vous lirez un fichier vide. Comment dissiper toute ambiguïté légitime de l'échec d'insertion de caractères et «échec» d'insérer des caractères parce que le fichier est vide?Vous pourriez penser à rechercher explicitement un fichier vide, mais c'est plus de code et de 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 l'ostringstream ni sur 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 définit l'eofbit. Vérifiez ensuite la condition d'échec
file_stream.fail() && !file_stream.eof()
.Surtout, 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 échecs légitimes.la source
Quelque chose comme ça ne devrait pas être trop grave:
L'avantage ici est que nous faisons la réserve en premier afin de ne pas avoir à agrandir la chaîne pendant que nous lisons les choses. L'inconvénient est que nous le faisons char par char. Une version plus intelligente pourrait récupérer tout le buf de lecture, puis appeler underflow.
la source
Voici une version utilisant la nouvelle bibliothèque de système de fichiers avec une vérification d'erreur raisonnablement robuste:
la source
infile.open
peut également accepterstd::string
sans convertir avec.c_str()
filepath
n'est pas unstd::string
, c'est unstd::filesystem::path
. Il s'avère questd::ifstream::open
peut également accepter l'un de ceux-ci.std::filesystem::path
est implicitement convertible enstd::string
::open
fonction membre surstd::ifstream
qui acceptestd::filesystem::path
fonctionne comme si la::c_str()
méthode était appelée sur le chemin. Le sous::value_type
- jacent des chemins estchar
sous POSIX.Vous pouvez utiliser la fonction 'std :: getline' et spécifier 'eof' comme délimiteur. Le code qui en résulte est cependant un peu obscur:
la source
N'écrivez jamais dans le tampon const char * de std :: string. Plus jamais! Cela est une énorme erreur.
Réservez () de l'espace pour toute la chaîne de 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 presque sûr que tous les autres mécanismes portables et compatibles STL feront de même (mais peuvent paraître plus jolis).
la source
std::string
tampon; et je crois que cela a fonctionné correctement sur toutes les implémentations réelles avant celastd::string::data()
méthode pour modifier directement le tampon de chaîne sans recourir à des astuces comme&str[0]
.usage:
la source
Une fonction mise à jour qui s'appuie sur la solution de CTT:
Il existe deux différences importantes:
tellg()
n'est pas garanti de renvoyer le décalage en octets depuis le début du fichier. Au lieu de cela, comme l'a souligné Puzomor Croatia, il s'agit davantage d'un jeton qui peut être utilisé dans les appels fstream.gcount()
cependant le fait revenir la quantité d'octets non formatés dernier extrait. Nous ouvrons donc le fichier, extrayons et supprimons tout son contenu avecignore()
pour obtenir la taille du fichier, et construisons la chaîne de sortie en fonction de cela.Deuxièmement, nous évitons d'avoir à copier les données du fichier d'un
std::vector<char>
vers unstd::string
en écrivant directement dans la chaîne.En termes de performances, cela devrait être le plus rapide absolu, en allouant la chaîne de taille appropriée à l'avance et en appelant
read()
une fois. Fait intéressant, utiliserignore()
etcountg()
au lieu deate
ettellg()
sur gcc se compile à peu près à la même chose , petit à petit.la source
ifs.seekg(0)
au lieu deifs.clear()
(alors ça marche).la source