2012-08-09 14 views
7

Chciałbym przeczytać tylko ostatni wiersz pliku tekstowego (jestem na UNIX, można użyć Boost). Wszystkie znane mi metody wymagają skanowania całego pliku, aby uzyskać ostatnią linię, która w ogóle nie jest wydajna. Czy istnieje skuteczny sposób uzyskania tylko ostatniej linii?C++ najszybszy sposób na odczytanie tylko ostatniego wiersza pliku tekstowego?

Potrzebuję tego, aby był wystarczająco solidny, aby działał, nawet jeśli dany plik tekstowy jest ciągle dołączany do innego procesu.

+0

Czy istnieje * cokolwiek *, które jest odporne na fakt, że ktoś * nieustannie * modyfikuje plik? Jak byś zdefiniował "mocną" w takiej sytuacji? –

+1

@ user788171 powinieneś być w stanie szukać do końca i skanować wstecz dla terminatora linii. Prawdopodobnie sugerowałbym, żebyś nie używał tutaj surowego pliku, ponieważ brzmi bardziej jak chcesz fajkę. – oldrinb

Odpowiedz

15

Zastosowanie seekg aby przeskoczyć do końca pliku, a następnie odczytać z powrotem aż znajdziesz pierwszy znak nowej linii. Poniżej znajduje się przykładowy kod u góry mojej głowy za pomocą 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; 
} 

Poniżej znajduje się plik testowy. Udaje się to z pustymi, jednowierszowymi i wielowierszowymi danymi w pliku tekstowym.

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

Tak naprawdę przetestowałem to i nie działa. lastLine jest zawsze puste. – user788171

+3

Zabawne, testowałem to przed wysłaniem. Czy twój plik test.txt ma na końcu dodatkową pustą linię? – derpface

+0

To nie działa, ponieważ [pliki tekstowe powinny kończyć się nową linią] (https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline) i [wiele redaktorzy wstawiają tę postać automatycznie] (https://stackoverflow.com/questions/14171254/why-would-vim-add-a-new-line-at-the-end-of-a-file). – phinz

4

Przeskocz do tego miejsca i rozpocznij czytanie bloków wstecz, dopóki nie znajdziesz żadnych kryteriów dla linii. Jeśli ostatni blok nie "kończy się" linią, prawdopodobnie będziesz musiał również przeskanować do przodu (zakładając naprawdę długą linię w aktywnie dołączonym do pliku).

+0

jak dokładnie przeskoczysz do końca i zaczniesz czytać bloki do tyłu? – user788171

+0

@ user788171 Używając czegoś takiego jak istream :: seekg (0, ios_base :: end). Następnie możesz użyć polecenia seek w celu przejścia do przodu/do tyłu w strumieniu. – Yuushi

1

Można użyć seekg(), aby przejść do końca pliku i odczytać tyłu, Pseudo-kod jest podobny:

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" może? –

+0

@JesseDobry mój błąd, masz rację. – carter2000

0

Również borykałem się z problemem, ponieważ uruchomiłem kod uberwulu i otrzymałem pustą linię. Oto co znalazłem. Używam następujący plik .csv jako przykład:

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

Aby zrozumieć poleceń w kodzie, proszę zauważyć następujące lokalizacje i odpowiadające im znaki. (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

Wprawdzie odpowiedź z wykrzyku jest zdecydowanie poprawna, ale często zwraca nieoczekiwane wyniki. Powodem tego jest to, że przynajmniej w moim systemie operacyjnym (Mac OSX 10.9.5), wielu edytorów tekstu kończy swoje pliki znakiem końca wiersza.

Na przykład, gdy otwieram vim wpisać tylko jeden znak 'a' (No Return), a następnie zapisz plik będzie teraz zawierać (w hex):

61 0A 

Gdzie 61 to litera "a" i 0A to znak końca linii.

Oznacza to, że kod przez derpface zwróci pusty ciąg we wszystkich plikach utworzonych przez taki edytor tekstu.

Chociaż z pewnością mogę sobie wyobrazić przypadki, w których plik zakończony "linią końcową" powinien zwracać pusty ciąg, myślę, że ignorowanie ostatniego znaku końca wiersza byłoby bardziej odpowiednie w przypadku zwykłych plików tekstowych; jeśli plik zostanie zakończony znakiem "linii końcowej", zignorujemy go właściwie, a jeśli plik nie zostanie zakończony znakiem "linii końcowej", nie musimy go sprawdzać.

Mój kod do ignorowania ostatni znak pliku wejściowego jest:

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

które wyjście będzie:

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

Na pojedynczym pliku 'a'.

EDYCJA: Linia if((int)fin.tellg() <= 0){ w rzeczywistości powoduje problemy, jeśli plik jest zbyt duży (> 2 GB), ponieważ tellg nie zwraca tylko liczby znaków od początku pliku (tellg() function give wrong size of file?). Może być lepiej osobno przetestować na początek pliku fin.tellg()==tellgValueForStartOfFile i błędów fin.tellg()==-1. tellgValueForStartOfFile jest prawdopodobnie 0, ale lepszym sposobem na upewnienie będzie prawdopodobnie:

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

Początkowo ten został zaprojektowany, aby przeczytać ostatni wpis syslog. Biorąc pod uwagę, że ostatnią postacią przed EOF jest '\n', szukamy z powrotem następnego wystąpienia '\n', a następnie zapisujemy linię w ciągu znaków.

#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; 
} 
Powiązane problemy