2012-08-09 153 views
7

Me gustaría leer solo la última línea de un archivo de texto (estoy en UNIX, puedo usar Boost). Todos los métodos que conozco requieren escanear todo el archivo para obtener la última línea que no es eficiente en absoluto. ¿Hay una manera eficiente de obtener solo la última línea?C++ forma más rápida de leer solo la última línea de archivo de texto?

Además, necesito que sea lo suficientemente robusto como para que funcione incluso si el archivo de texto en cuestión se agrega constantemente a otro proceso.

+0

¿Hay * algo * que sea robusto en el hecho de que alguien * constantemente * modifique el archivo? ¿Cómo definirías "robusto" en esa circunstancia? –

+1

@ user788171 debe poder buscar hasta el final y buscar un terminador de línea hacia atrás. Sin embargo, probablemente te sugiero que no uses un archivo en bruto, ya que suena más como si quisieras un tubo. – oldrinb

Respuesta

15

Uso seekg para saltar hasta el final del archivo, a continuación, vuelve a leer hasta que encuentre la primera línea nueva. A continuación se muestra un código de muestra de la parte superior de mi cabeza con MSVC.

#include <iostream> 
#include <fstream> 
#include <sstream> 

using namespace std; 

int main() 
{ 
    string filename = "test.txt"; 
    ifstream fin; 
    fin.open(filename); 
    if(fin.is_open()) { 
     fin.seekg(-1,ios_base::end);    // go to one spot before the EOF 

     bool keepLooping = true; 
     while(keepLooping) { 
      char ch; 
      fin.get(ch);       // Get current byte's data 

      if((int)fin.tellg() <= 1) {    // If the data was at or before the 0th byte 
       fin.seekg(0);      // The first line is the last line 
       keepLooping = false;    // So stop there 
      } 
      else if(ch == '\n') {     // If the data was a newline 
       keepLooping = false;    // Stop at the current position. 
      } 
      else {         // If the data was neither a newline nor at the 0 byte 
       fin.seekg(-2,ios_base::cur);  // Move to the front of that data, then to the front of the data before it 
      } 
     } 

     string lastLine;    
     getline(fin,lastLine);      // Read the current line 
     cout << "Result: " << lastLine << '\n';  // Display it 

     fin.close(); 
    } 

    return 0; 
} 

Y a continuación hay un archivo de prueba. Lo logra con datos vacíos, de una sola línea y multilíneas en el archivo de texto.

This is the first line. 
Some stuff. 
Some stuff. 
Some stuff. 
This is the last line. 
+1

Entonces, en realidad lo probé y en realidad no funciona. lastLine siempre está vacío. – user788171

+3

Es curioso, lo probé antes de publicarlo. ¿Tu test.txt tiene una línea en blanco adicional al final? – derpface

+0

Esto no funciona para mí ya que [los archivos de texto deben terminar con un nuevo carácter de línea] (https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline) y [muchos los editores insertan ese carácter automáticamente] (https://stackoverflow.com/questions/14171254/why-would-vim-add-a-new-line-at-the-end-of-a-file). – phinz

4

Salta hasta el final y comienza a leer bloques hacia atrás hasta que encuentres los criterios para una línea. Si el último bloque no "finaliza" con una línea, probablemente también deba intentar escanear hacia adelante (asumiendo una línea muy larga en un archivo adjuntado activamente al archivo).

+0

¿cómo exactamente saltas hasta el final y comienzas a leer bloques hacia atrás? – user788171

+0

@ user788171 Al usar algo como istream :: seekg (0, ios_base :: end). Luego puede usar seekg desde allí para avanzar o retroceder en la transmisión. – Yuushi

1

Puede utilizar seekg() para saltar al final del archivo, y leer hacia atrás, el pseudo-código es como:

ifstream fs 
fs.seekg(ios_base::end) 
bytecount = fs.tellg() 
index = 1 
while true 
    fs.seekg(bytecount - step * index, ios_base::beg) 
    fs.read(buf, step) 
    if endlinecharacter in buf 
     get endlinecharacter's index, said ei 
     fs.seekg(bytecount - step*index + ei) 
     fs.read(lastline, step*index - ei) 
     break 
    ++index 
+0

'seekg' ¿quizás? –

+0

@JesseGood mi error, tienes razón. – carter2000

0

también estaba luchando en el problema porque me encontré con código de uberwulu y también tengo línea en blanco. Esto es lo que encontré. Estoy utilizando el siguiente archivo .csv como ejemplo:

date  test1 test2 
20140908  1  2 
20140908  11  22 
20140908  111 235 

Para entender los comandos en el código, por favor, observe las siguientes ubicaciones y sus caracteres correspondientes. (Loc, char): ... (63, '3'), (64, '5'), (65, -), (66, '\ n'), (EOF, -).

#include<iostream> 
#include<string> 
#include<fstream> 

using namespace std; 

int main() 
{ 
    std::string line; 
    std::ifstream infile; 
    std::string filename = "C:/projects/MyC++Practice/Test/testInput.csv"; 
    infile.open(filename); 

    if(infile.is_open()) 
    { 
     char ch; 
     infile.seekg(-1, std::ios::end);  // move to location 65 
     infile.get(ch);       // get next char at loc 66 
     if (ch == '\n') 
     { 
      infile.seekg(-2, std::ios::cur); // move to loc 64 for get() to read loc 65 
      infile.seekg(-1, std::ios::cur); // move to loc 63 to avoid reading loc 65 
      infile.get(ch);      // get the char at loc 64 ('5') 
      while(ch != '\n')     // read each char backward till the next '\n' 
      { 
       infile.seekg(-2, std::ios::cur);  
       infile.get(ch); 
      } 
      string lastLine; 
      std::getline(infile,lastLine); 
      cout << "The last line : " << lastLine << '\n';  
     } 
     else 
      throw std::exception("check .csv file format"); 
    } 
    std::cin.get(); 
    return 0; 
} 
1

Si bien la respuesta por derpface es definitivamente correcta, a menudo devuelve resultados inesperados. La razón de esto es que, al menos en mi sistema operativo (Mac OSX 10.9.5), muchos editores de texto terminan sus archivos con un carácter de 'línea final'.

Por ejemplo, cuando abro vim, escriba sólo el único carácter 'a' (sin retorno), y guardar, el archivo será ahora contiene (en hexadecimal):

61 0A 

Donde 61 es la letra 'a' y 0A es un carácter de fin de línea.

Esto significa que el código por derpface devolverá una cadena vacía en todos los archivos creados por dicho editor de texto.

Si bien puedo imaginar casos en que un archivo terminado con una 'línea final' debe devolver la cadena vacía, creo que ignorar el último carácter 'línea final' sería más apropiado cuando se trata de archivos de texto regulares; si el archivo termina con un carácter de 'línea final', lo ignoramos adecuadamente, y si el archivo no termina con un carácter de 'línea final', no es necesario que lo verifiquemos.

Mi código para ignorar el último carácter del archivo de entrada es:

#include <iostream> 
#include <string> 
#include <fstream> 
#include <iomanip> 

int main() { 
    std::string result = ""; 
    std::ifstream fin("test.txt"); 

    if(fin.is_open()) { 
     fin.seekg(0,std::ios_base::end);  //Start at end of file 
     char ch = ' ';      //Init ch not equal to '\n' 
     while(ch != '\n'){ 
      fin.seekg(-2,std::ios_base::cur); //Two steps back, this means we 
               //will NOT check the last character 
      if((int)fin.tellg() <= 0){  //If passed the start of the file, 
       fin.seekg(0);     //this is the start of the line 
       break; 
      } 
      fin.get(ch);      //Check the next character 
     } 

     std::getline(fin,result); 
     fin.close(); 

     std::cout << "final line length: " << result.size() <<std::endl; 
     std::cout << "final line character codes: "; 
     for(size_t i =0; i<result.size(); i++){ 
      std::cout << std::hex << (int)result[i] << " "; 
     } 
     std::cout << std::endl; 
     std::cout << "final line: " << result <<std::endl; 
    } 

    return 0; 
} 

Cuál sería:

final line length: 1 
final line character codes: 61 
final line: a 

En la 'a' fila india.

EDITAR: La línea if((int)fin.tellg() <= 0){ en realidad causa problemas si el archivo es demasiado grande (> 2 GB), ya que no solo devuelve el número de caracteres desde el inicio del archivo (tellg() function give wrong size of file?). Puede ser mejor probar por separado para el inicio del archivo fin.tellg()==tellgValueForStartOfFile y para los errores fin.tellg()==-1. El tellgValueForStartOfFile es probablemente 0, pero una mejor manera de asegurarse de que probablemente sería:

fin.seekg (0, is.beg); 
tellgValueForStartOfFile = fin.tellg(); 
0

Inicialmente esto fue diseñado para leer la última entrada de syslog. Dado que el último carácter antes del EOF es '\n' buscamos de nuevo para encontrar la siguiente aparición de '\n' y luego almacenamos la línea en una cadena.

#include <fstream> 
#include <iostream> 

int main() 
{ 
    const std::string filename = "test.txt"; 
    std::ifstream fs; 
    fs.open(filename.c_str(), std::fstream::in); 
    if(fs.is_open()) 
    { 
    //Got to the last character before EOF 
    fs.seekg(-1, std::ios_base::end); 
    if(fs.peek() == '\n') 
    { 
     //Start searching for \n occurrences 
     fs.seekg(-1, std::ios_base::cur); 
     int i = fs.tellg(); 
     for(i;i > 0; i--) 
     { 
     if(fs.peek() == '\n') 
     { 
      //Found 
      fs.get(); 
      break; 
     } 
     //Move one character back 
     fs.seekg(i, std::ios_base::beg); 
     } 
    } 
    std::string lastline; 
    getline(fs, lastline); 
    std::cout << lastline << std::endl; 
    } 
    else 
    { 
    std::cout << "Could not find end line character" << std::endl; 
    } 
    return 0; 
} 
Cuestiones relacionadas