2012-08-09 13 views
7

Mi piacerebbe leggere solo l'ultima riga di un file di testo (sono su UNIX, posso usare Boost). Tutti i metodi che conosco richiedono la scansione dell'intero file per ottenere l'ultima riga che non è affatto efficiente. C'è un modo efficace per ottenere solo l'ultima riga?C++ modo più veloce per leggere solo l'ultima riga del file di testo?

Inoltre, ho bisogno che questo sia abbastanza robusto da funzionare anche se il file di testo in questione viene costantemente aggiunto da un altro processo.

+0

C'è * qualcosa * che è forte nel fatto che qualcuno * costantemente * modifica il file? Come definiresti "robusto" in quella circostanza? –

+1

@ user788171 dovresti essere in grado di cercare fino alla fine e scansionare all'indietro per un terminatore di riga. Probabilmente ti suggerirei di non usare qui un file raw, comunque, dal momento che sembra più che tu voglia un pipe. – oldrinb

risposta

15

Usa seekg per passare alla fine del file, quindi leggere indietro finché non trovi la prima nuova riga. Di seguito è riportato un codice di esempio dalla parte superiore della mia testa utilizzando 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; 
} 

E di seguito è un file di prova. Succede con i dati vuoti, a una riga e multilinea nel file di testo.

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

Quindi, in realtà ho provato questo e in realtà non funziona. lastLine è sempre vuoto. – user788171

+3

Divertente, l'ho provato prima di postare. Il tuo test.txt ha una riga bianca in più alla fine? – derpface

+0

Questo non funziona perché [i file di testo dovrebbero terminare con un nuovo carattere di linea] (https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newewline) e [molti gli editor inseriscono automaticamente quel carattere] (https://stackoverflow.com/questions/14171254/why-would-vim-add-a-new-line-at-the-end-of-a-file). – phinz

4

Salta fino alla fine e inizia a leggere i blocchi all'indietro finché non trovi i criteri per una linea. Se l'ultimo blocco non "termina" con una linea, probabilmente dovrai provare a scansionare anche in avanti (assumendo una linea molto lunga in un file aggiunto attivamente).

+0

in che modo esattamente salti fino alla fine e inizi a leggere i blocchi all'indietro? – user788171

+0

@ user788171 Usando qualcosa come istream :: seekg (0, ios_base :: end). È quindi possibile utilizzare seekg da lì per spostarsi avanti/indietro nello stream. – Yuushi

1

È possibile utilizzare seekg() per saltare alla fine del file, e leggere a ritroso, la pseudo-codice è come:

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' forse? –

+0

@JesseGood mio errore, hai ragione. – carter2000

0

Ho anche faticato sul problema perché ho eseguito il codice di uberwulu e ho anche ottenuto una riga vuota. Ecco cosa ho trovato. Sto usando il seguente file .csv come esempio:

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

Per comprendere i comandi nel codice, si prega di notare le seguenti posizioni ed i loro caratteri corrispondenti. (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

Mentre la risposta di derpface è decisamente corretta, restituisce spesso risultati imprevisti. Il motivo è che, almeno sul mio sistema operativo (Mac OSX 10.9.5), molti editor di testo terminano i loro file con un carattere di "fine linea".

Per esempio, quando ho aperto vim, digitare solo il singolo carattere 'a' (senza ritorno), e salvare, il file verrà ora contiene (in esadecimale):

61 0A 

dove 61 è la lettera 'a' e 0A è un carattere di fine riga.

Ciò significa che il codice di derpface restituirà una stringa vuota su tutti i file creati da tale editor di testo.

Mentre posso certamente immaginare casi in cui un file terminato con un 'end line' dovrebbe restituire la stringa vuota, penso che ignorare l'ultimo carattere 'end line' sarebbe più appropriato quando si ha a che fare con file di testo normali; se il file è terminato da un carattere 'end line', lo ignoriamo correttamente, e se il file non viene terminato da un carattere 'end line' non è necessario controllarlo.

Il mio codice per ignorare l'ultimo carattere del file di input è:

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

quale uscita sarà:

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

Sul 'un' singolo file.

MODIFICA: la riga if((int)fin.tellg() <= 0){ causa effettivamente problemi se il file è troppo grande (> 2 GB), perché tellg non restituisce solo il numero di caratteri dall'inizio del file (tellg() function give wrong size of file?). Potrebbe essere meglio testare separatamente l'inizio del file fin.tellg()==tellgValueForStartOfFile e per gli errori fin.tellg()==-1. Il tellgValueForStartOfFile è probabilmente 0, ma un modo migliore di fare in modo probabilmente sarebbe:

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

Inizialmente questo è stato progettato per leggere l'ultima voce syslog. Dato che l'ultimo carattere prima dell'EOF è '\n', cerchiamo di trovare l'occorrenza successiva di '\n' e quindi memorizziamo la linea in una stringa.

#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; 
} 
Problemi correlati