2012-08-09 4 views
7

Я хотел бы прочитать только последнюю строку текстового файла (я на UNIX, могу использовать Boost). Все методы, которые я знаю, требуют сканирования по всему файлу, чтобы получить последнюю строку, которая неэффективна вообще. Есть ли эффективный способ получить только последнюю строку?C++ быстрый способ чтения только последней строки текстового файла?

Кроме того, мне нужно, чтобы это было достаточно надежным, чтобы оно работало, даже если текстовый файл постоянно добавляется к другому процессу.

+0

Есть ли что-либо *, что является прочным в факте кого-то * постоянно * изменение файла? Как бы вы даже определили «здравый» в этом обстоятельстве? –

+1

@ user788171 вы должны быть в состоянии искать до конца и отсканировать назад для терминатора линии. Однако я бы предположил, что вы не используете здесь необработанный файл, поскольку это похоже на то, что вы хотите использовать канал. – oldrinb

ответ

15

Использование seekg для перехода к концу файла, а затем считаны пока вы не найдете первую новую строку. Ниже приведен пример кода с верхней части головы с помощью 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; 
} 

И ниже тестовый файл. Он преуспевает в пустых, однострочных и многострочных данных в текстовом файле.

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

Итак, я действительно протестировал это, и на самом деле это не работает. lastLine всегда пуст. – user788171

+3

Смешно, я тестировал его перед публикацией. У вашего test.txt есть дополнительная пустая строка в конце? – derpface

+0

Это не работает для меня, так как [textfiles должен заканчиваться новым символом строки] (https://stackoverflow.com/questions/729692/why-should-text-files-end-with-a-newline) и [много редакторы автоматически вставляют этот символ] (https://stackoverflow.com/questions/14171254/why-would-vim-add-a-new-line-at-the-end-of-a-file). – phinz

4

Переход к концу и начало считывания блоков назад, пока вы не найдете все, что подходит для вашей линии. Если последний блок не «заканчивается» линией, вам, вероятно, также придется попробовать и сканировать вперед (при условии, что на самом деле длинная строка активно добавляется к файлу).

+0

как именно вы прыгаете до конца и начинаете читать блоки назад? – user788171

+0

@ user788171 Используя что-то вроде istream :: seekg (0, ios_base :: end). Затем вы можете использовать seekg для перемещения вперед/назад в потоке. – Yuushi

1

Вы можете использовать seekg(), чтобы перейти в конец файла и читать назад, Псевдо-код, как:

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' возможно? –

+0

@Jesse Хорошая моя ошибка, вы правы. – carter2000

0

Я также боролся с проблемой, потому что я выполнил код uberwulu, а также получил пустую строку. Вот что я нашел. Я использую следующий файл .csv в качестве примера:

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

Чтобы понять команды в коде, пожалуйста, обратите внимание на следующие места и соответствующие им символы. (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

Хотя ответ derpface определенно верен, он часто возвращает неожиданные результаты. Причина этого заключается в том, что, по крайней мере, в моей операционной системе (Mac OSX 10.9.5) многие текстовые редакторы завершают свои файлы символом «конечной линии».

Например, когда я открываю Vim, введите только один символ «а» (без возврата), и сохраните файл теперь будет содержать (в шестнадцатеричном виде):

61 0A 

Где 61 это буква 'a' и 0A - символ конца строки.

Это означает, что код derpface вернет пустую строку для всех файлов, созданных таким текстовым редактором.

Хотя я могу, конечно, представить случаи, когда файл, заканчивающийся «конечной линией», должен возвращать пустую строку, я думаю, что игнорирование последнего символа «конечной строки» было бы более уместным при работе с обычными текстовыми файлами; если файл заканчивается символом «конечной линии», мы его должным образом игнорируем, и если файл не заканчивается символом «конечной линии», нам не нужно его проверять.

Мой код для игнорирования последнего символа входного файла:

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

Что будет:

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

На одном 'а' файла.

EDIT: строка if((int)fin.tellg() <= 0){ действительно вызывает проблемы, если файл слишком большой (> 2 ГБ), потому что tellg не просто возвращает количество символов с начала файла (tellg() function give wrong size of file?). Может быть, лучше провести отдельную проверку для начала файла fin.tellg()==tellgValueForStartOfFile и для ошибок fin.tellg()==-1. tellgValueForStartOfFile вероятно 0, но лучший способ убедиться, вероятно, будет:

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

Первоначально это был разработан, чтобы прочитать последнюю запись в системном журнале. Учитывая, что последний символ перед EOF равен '\n', мы ищем его, чтобы найти следующее вхождение '\n', а затем мы сохраняем строку в строке.

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