Lea el archivo línea por línea usando ifstream en C ++

612

Los contenidos de file.txt son:

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

Donde 5 3hay un par de coordenadas. ¿Cómo proceso estos datos línea por línea en C ++?

Puedo obtener la primera línea, pero ¿cómo obtengo la siguiente línea del archivo?

ifstream myfile;
myfile.open ("text.txt");
limón
fuente

Respuestas:

916

Primero, haga un ifstream:

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

Los dos métodos estándar son:

  1. Suponga que cada línea consta de dos números y lea token por token:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
    
  2. Análisis basado en líneas, utilizando secuencias de cadena:

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

No debe mezclar (1) y (2), ya que el análisis basado en tokens no engulle nuevas líneas, por lo que puede terminar con líneas vacías espurias si lo usa getline()después de que la extracción basada en tokens lo lleve al final de un línea ya.

Kerrek SB
fuente
1
@EdwardKarak: No entiendo qué significa "comas como token". Las comas no representan enteros.
Kerrek SB
8
El OP utilizó un espacio para delimitar los dos enteros. Quería saber si while (infile >> a >> b) funcionaría si el OP usara como coma un delimitador, porque ese es el escenario en mi propio programa
Edward Karak
30
@EdwardKarak: Ah, entonces cuando dijiste "token" querías decir "delimitador". Derecha. Con una coma, diría:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB
11
@KerrekSB: Eh. Estaba equivocado. No sabía que podía hacer eso. Puede que tenga un código propio para reescribir.
Mark H
44
Para una explicación de la while(getline(f, line)) { }construcción y con respecto al manejo de errores, eche un vistazo a este (mi) artículo: gehrcke.de/2011/06/… (Creo que no necesito tener mala conciencia publicando esto aquí, incluso un poco antes fecha esta respuesta).
Dr. Jan-Philip Gehrcke
175

Use ifstreampara leer datos de un archivo:

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

Si realmente necesita leer línea por línea, haga esto:

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

Pero probablemente solo necesite extraer pares de coordenadas:

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

Actualizar:

En su código que utiliza ofstream myfile;, sin embargo, significa oin . Si desea leer desde el archivo (entrada) use . Si quiere leer y escribir, use .ofstreamoutputifstreamfstream

K-Ballo
fuente
8
Su solución ha mejorado un poco: su variable de línea no es visible después de la lectura del archivo en contraste con la segunda solución de Kerrek SB, que también es una solución buena y simple.
DanielTuzes
3
getlineestá a la string vista , así que no olvides el#include <string>
mxmlnkn
56

La lectura de un archivo línea por línea en C ++ se puede hacer de diferentes maneras.

[Rápido] Loop con std :: getline ()

El enfoque más simple es abrir un std :: ifstream y un bucle usando llamadas std :: getline (). El código es limpio y fácil de entender.

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

[Rápido] Utilice el archivo_description_source de Boost

Otra posibilidad es usar la biblioteca Boost, pero el código se vuelve un poco más detallado. El rendimiento es bastante similar al código anterior (bucle con 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();
        }
    }
}

[Más rápido] Usar código C

Si el rendimiento es crítico para su software, puede considerar usar el lenguaje C. Este código puede ser 4-5 veces más rápido que las versiones de C ++ anteriores, consulte el punto de referencia a continuación

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

Punto de referencia: ¿cuál es más rápido?

He hecho algunos puntos de referencia de rendimiento con el código anterior y los resultados son interesantes. He probado el código con archivos ASCII que contienen 100,000 líneas, 1,000,000 líneas y 10,000,000 líneas de texto. Cada línea de texto contiene 10 palabras en promedio. El programa se compila con -O3optimización y su salida se reenvía /dev/nullpara eliminar la variable de tiempo de registro de la medición. Por último, pero no menos importante, cada fragmento de código registra cada línea con la printf()función para mantener la coherencia.

Los resultados muestran el tiempo (en ms) que tomó cada fragmento de código para leer los archivos.

La diferencia de rendimiento entre los dos enfoques de C ++ es mínima y no debería hacer ninguna diferencia en la práctica. El rendimiento del código C es lo que hace que el punto de referencia sea impresionante y puede cambiar el juego en términos de velocidad.

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

ingrese la descripción de la imagen aquí

HugoTeixeira
fuente
1
¿Qué sucede si elimina la sincronización de C ++ con C en las salidas de la consola? Es posible que se está midiendo una desventaja conocida del comportamiento predeterminado de std::coutfrente printf.
user4581301
2
Gracias por traer esta preocupación. He rehecho las pruebas y el rendimiento sigue siendo el mismo. He editado el código para usar la printf()función en todos los casos por coherencia. También he intentado usar std::couten todos los casos y esto no hizo absolutamente ninguna diferencia. Como acabo de describir en el texto, la salida del programa pasa a /dev/nullmodo que no se mide el tiempo para imprimir las líneas.
HugoTeixeira
66
Groovy Gracias. Me pregunto dónde está la desaceleración.
user4581301
44
Hola @HugoTeixeira Sé que este es un hilo antiguo, intenté replicar sus resultados y no pude ver ninguna diferencia significativa entre c y c ++ github.com/simonsso/readfile_benchmarks
Simson
De forma predeterminada, las secuencias de entrada y salida de C ++ se sincronizan con cstdio. Deberías haber intentado con la configuración std::ios_base::sync_with_stdio(false). Supongo que habría obtenido un rendimiento mucho mejor (aunque no está garantizado ya que está definido por la implementación cuando se desactiva la sincronización).
Fareanor
11

Dado que sus coordenadas pertenecen juntas como pares, ¿por qué no escribir una estructura para ellas?

struct CoordinatePair
{
    int x;
    int y;
};

Luego puede escribir un operador de extracción sobrecargado para istreams:

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

    return is;
}

Y luego puedes leer un archivo de coordenadas directamente en un vector como este:

#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
}
Martin Broadhurst
fuente
1
¿Qué sucede cuando no es posible leer dos inttokens de la transmisión operator>>? ¿Cómo se puede hacer que funcione con un analizador de retroceso (es decir, cuando operator>>falla, retroceda el flujo a la posición anterior y devuelva falso o algo así)?
fferri
Si no es posible leer dos inttokens, la issecuencia se evaluará falsey el ciclo de lectura terminará en ese punto. Puede detectar esto operator>>al verificar el valor de retorno de las lecturas individuales. Si desea revertir la transmisión, debe llamar is.clear().
Martin Broadhurst
en el operator>>que es más correcto decir is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;ya que de lo contrario se está asumiendo que su flujo de entrada está en el modo de omisión de espacios en blanco.
Darko Veberic
7

Ampliando la respuesta aceptada, si la entrada es:

1,NYC
2,ABQ
...

aún podrá aplicar la misma lógica, como esta:

#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();
gsamaras
fuente
2

Aunque no es necesario cerrar el archivo manualmente, es una buena idea hacerlo si el alcance de la variable del archivo es mayor:

    ifstream infile(szFilePath);

    for (string line = ""; getline(infile, line); )
    {
        //do something with the line
    }

    if(infile.is_open())
        infile.close();
Vijay Bansal
fuente
No estoy seguro de que esto merezca un voto negativo. OP solicitó una forma de obtener cada línea. Esta respuesta hace eso y da un gran consejo para asegurarse de que el archivo se cierre. Para un programa simple, puede no ser necesario, pero como mínimo debe formarse un GRAN hábito. Tal vez podría mejorarse agregando algunas líneas de código para procesar las líneas individuales que extrae, pero en general es la respuesta más simple a la pregunta de los OP.
Xandor
2

Esta respuesta es para Visual Studio 2017 y si desea leer del archivo de texto qué ubicación es relativa a su aplicación de consola compilada.

primero coloque su archivo de texto (test.txt en este caso) en su carpeta de soluciones. Después de compilar, mantenga el archivo de texto en la misma carpeta con applicationName.exe

C: \ Users \ "nombre de usuario" \ source \ repos \ "solutionName" \ "solutionName"

#include <iostream>
#include <fstream>

using namespace std;
int main()
{
    ifstream inFile;
    // open the file stream
    inFile.open(".\\test.txt");
    // check if opening a file failed
    if (inFile.fail()) {
        cerr << "Error opeing a file" << endl;
        inFile.close();
        exit(1);
    }
    string line;
    while (getline(inFile, line))
    {
        cout << line << endl;
    }
    // close the file stream
    inFile.close();
}
Universus
fuente
1

Esta es una solución general para cargar datos en un programa C ++ y utiliza la función readline. Esto podría modificarse para los archivos CSV, pero el delimitador es un espacio aquí.

int n = 5, p = 2;

int X[n][p];

ifstream myfile;

myfile.open("data.txt");

string line;
string temp = "";
int a = 0; // row index 

while (getline(myfile, line)) { //while there is a line
     int b = 0; // column index
     for (int i = 0; i < line.size(); i++) { // for each character in rowstring
          if (!isblank(line[i])) { // if it is not blank, do this
              string d(1, line[i]); // convert character to string
              temp.append(d); // append the two strings
        } else {
              X[a][b] = stod(temp);  // convert string to double
              temp = ""; // reset the capture
              b++; // increment b cause we have a new number
        }
    }

  X[a][b] = stod(temp);
  temp = "";
  a++; // onto next row
}
mjr2000
fuente