789 votes

Lire un fichier ligne par ligne en utilisant ifstream en C++.

Le contenu de file.txt est :

5 3
6 4
7 1
10 5
11 6
12 3
12 4

5 3 est une paire de coordonnées. Comment traiter ces données ligne par ligne en C++ ?

Je suis capable d'obtenir la première ligne, mais comment puis-je obtenir la ligne suivante du fichier ?

ifstream myfile;
myfile.open ("file.txt");

1153voto

Kerrek SB Points 194696

D'abord, faites un ifstream :

#include <fstream>
std::ifstream infile("thefile.txt");

Les deux méthodes standard sont :

  1. Supposons que chaque ligne soit composée de deux chiffres et qu'elle soit lue jeton par jeton :

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
  2. Analyse syntaxique basée sur les lignes, utilisant des flux de chaînes :

    #include <sstream>
    #include <string>
    
    std::string line;
    while (std::getline(infile, line))
    {
        std::istringstream iss(line);
        int a, b;
        if (!(iss >> a >> b)) { break; } // error
    
        // process pair (a,b)
    }

Vous ne devriez pas mélanger (1) et (2), car l'analyse syntaxique basée sur les jetons ne prend pas en compte les nouvelles lignes, et vous risquez donc de vous retrouver avec des lignes vides si vous utilisez getline() après que l'extraction basée sur les jetons vous ait déjà mené au bout d'une ligne.

1 votes

@EdwardKarak : Je ne comprends pas ce que signifie "virgules comme jeton". Les virgules ne représentent pas des entiers.

8 votes

Le PO a utilisé un espace pour délimiter les deux entiers. Je voulais savoir si while (infile >> a >> b) fonctionnerait si le PO utilisait a comme délimiteur de virgule, car c'est le scénario de mon propre programme.

36 votes

@EdwardKarak : Ah, donc quand vous avez dit "token" vous vouliez dire "delimiter". Exact. Avec une virgule, tu dirais : int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))

222voto

K-ballo Points 44794

Utilisez ifstream pour lire les données d'un fichier :

std::ifstream input( "filename.ext" );

Si vous avez vraiment besoin de lire ligne par ligne, faites-le :

for( std::string line; getline( input, line ); )
{
    ...for each line in input...
}

Mais vous n'avez probablement besoin que d'extraire des paires de coordonnées :

int x, y;
input >> x >> y;

Mise à jour :

Dans votre code, vous utilisez ofstream myfile; Toutefois, l o en ofstream signifie output . Si vous voulez lire à partir du fichier (entrée), utilisez ifstream . Si vous voulez à la fois lire et écrire, utilisez fstream .

11 votes

Votre solution est un peu améliorée : votre variable de ligne n'est pas visible après la lecture du fichier contrairement à la deuxième solution de Kerrek SB qui est une bonne et simple solution aussi.

12 votes

getline est en string voir alors n'oubliez pas le #include <string>

160voto

HugoTeixeira Points 2545

La lecture d'un fichier ligne par ligne en C++ peut se faire de différentes manières.

[Rapide] Boucle avec std::getline()

L'approche la plus simple est d'ouvrir un std::ifstream et de boucler en utilisant des appels std::getline(). Le code est propre et facile à comprendre.

#include <fstream>

std::ifstream file(FILENAME);
if (file.is_open()) {
    std::string line;
    while (std::getline(file, line)) {
        // using printf() in all tests for consistency
        printf("%s", line.c_str());
    }
    file.close();
}

[Rapide] Utiliser le fichier_description_source de Boost.

Une autre possibilité consiste à utiliser la bibliothèque Boost, mais le code est un peu plus verbeux. Les performances sont assez similaires au code ci-dessus (Loop avec std::getline()).

#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <fcntl.h>

namespace io = boost::iostreams;

void readLineByLineBoost() {
    int fdr = open(FILENAME, O_RDONLY);
    if (fdr >= 0) {
        io::file_descriptor_source fdDevice(fdr, io::file_descriptor_flags::close_handle);
        io::stream <io::file_descriptor_source> in(fdDevice);
        if (fdDevice.is_open()) {
            std::string line;
            while (std::getline(in, line)) {
                // using printf() in all tests for consistency
                printf("%s", line.c_str());
            }
            fdDevice.close();
        }
    }
}

[Le plus rapide] Utiliser le code C

Si les performances sont essentielles pour votre logiciel, vous pouvez envisager d'utiliser le langage C. Ce code peut être 4 à 5 fois plus rapide que les versions C++ ci-dessus, voir le benchmark ci-dessous

FILE* fp = fopen(FILENAME, "r");
if (fp == NULL)
    exit(EXIT_FAILURE);

char* line = NULL;
size_t len = 0;
while ((getline(&line, &len, fp)) != -1) {
    // using printf() in all tests for consistency
    printf("%s", line);
}
fclose(fp);
if (line)
    free(line);

Benchmark -- Lequel est le plus rapide ?

J'ai effectué quelques tests de performance avec le code ci-dessus et les résultats sont intéressants. J'ai testé le code avec des fichiers ASCII qui contiennent 100 000 lignes, 1 000 000 lignes et 10 000 000 lignes de texte. Chaque ligne de texte contient en moyenne 10 mots. Le programme est compilé avec -O3 et sa sortie est transmise à /dev/null afin de supprimer la variable de temps d'enregistrement de la mesure. Enfin, et ce n'est pas le moins important, chaque morceau de code enregistre chaque ligne avec la balise printf() par souci de cohérence.

Les résultats montrent le temps (en ms) que chaque morceau de code a pris pour lire les fichiers.

La différence de performance entre les deux approches C++ est minime et ne devrait pas faire de différence en pratique. La performance du code C est ce qui rend le benchmark impressionnant et peut changer la donne en termes de vitesse.

                             10K lines     100K lines     1000K lines
Loop with std::getline()         105ms          894ms          9773ms
Boost code                       106ms          968ms          9561ms
C code                            23ms          243ms          2397ms

enter image description here

10 votes

Que se passe-t-il si vous supprimez la synchronisation du C++ avec le C sur les sorties de la console ? Vous pourriez être en train de mesurer un inconvénient connu du comportement par défaut de std::cout vs printf .

7 votes

Merci d'avoir soulevé ce problème. J'ai refait les tests et les performances sont toujours les mêmes. J'ai modifié le code pour utiliser le printf() dans tous les cas, par souci de cohérence. J'ai également essayé d'utiliser std::cout dans tous les cas et cela n'a fait absolument aucune différence. Comme je viens de le décrire dans le texte, la sortie du programme va à /dev/null donc le temps d'impression des lignes n'est pas mesuré.

9 votes

Groovy. Merci. Je me demande où est le ralentissement.

19voto

Martin Broadhurst Points 3777

Puisque vos coordonnées vont ensemble par paires, pourquoi ne pas écrire une structure pour elles ?

struct CoordinatePair
{
    int x;
    int y;
};

Vous pouvez alors écrire un opérateur d'extraction surchargé pour les istreams :

std::istream& operator>>(std::istream& is, CoordinatePair& coordinates)
{
    is >> coordinates.x >> coordinates.y;

    return is;
}

Et puis vous pouvez lire un fichier de coordonnées directement dans un vecteur comme ceci :

#include <fstream>
#include <iterator>
#include <vector>

int main()
{
    char filename[] = "coordinates.txt";
    std::vector<CoordinatePair> v;
    std::ifstream ifs(filename);
    if (ifs) {
        std::copy(std::istream_iterator<CoordinatePair>(ifs), 
                std::istream_iterator<CoordinatePair>(),
                std::back_inserter(v));
    }
    else {
        std::cerr << "Couldn't open " << filename << " for reading\n";
    }
    // Now you can work with the contents of v
}

2 votes

Que se passe-t-il quand il n'est pas possible de lire deux int des jetons du flux dans operator>> ? Comment peut-on le faire fonctionner avec un analyseur syntaxique à retour arrière (c'est-à-dire lorsque operator>> échoue, ramène le flux à la position précédente et renvoie false ou quelque chose comme ça) ?

1 votes

S'il n'est pas possible de lire deux int les jetons, alors le is sera évalué à false et la boucle de lecture se terminera à ce point. Vous pouvez détecter cela dans operator>> en vérifiant la valeur de retour de chaque lecture. Si vous voulez annuler le flux, vous devez appeler is.clear() .

1 votes

Dans le operator>> il est plus correct de dire is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws; car sinon vous supposez que votre flux d'entrée est en mode saut d'espace.

10voto

gsamaras Points 9567

En développant la réponse acceptée, si l'entrée est :

1,NYC
2,ABQ
...

vous pourrez toujours appliquer la même logique, comme ceci :

#include <fstream>

std::ifstream infile("thefile.txt");
if (infile.is_open()) {
    int number;
    std::string str;
    char c;
    while (infile >> number >> c >> str && c == ',')
        std::cout << number << " " << str << "\n";
}
infile.close();

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