C++ самый быстрый способ прочитать только последнюю строку текстового файла?

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

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

6 ответов


используйте 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.

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


вы можете использовать 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

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

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

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

в одном файле "a".

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

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

Я также боролся с проблемой, потому что я запустил код убервулу, а также получил пустую строку. Вот что я нашел. Я использую следующее .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;
}  

первоначально это было предназначено для чтения последней записи системного журнала. Учитывая, что последний символ перед 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;
}