C++读取文本文件最后一行的最快方法是什么?

14

我想读取文本文件的最后一行(我在UNIX上,可以使用Boost)。 我所知道的所有方法都需要扫描整个文件以获得最后一行,这不是很有效率。 有没有一种有效的方法只获取最后一行?

此外,我需要这个方法足够强大,即使涉及的文本文件正在被另一个进程持续添加,它也能正常工作。


在某人不断修改文件的情况下,有什么东西是真正健壮的吗?在这种情况下,你如何定义“健壮”? - Nicol Bolas
1
@user788171 你应该能够寻找到结尾并向后扫描以查找行终止符。然而,我建议你不要在这里使用原始文件,因为这听起来更像是你想要一个管道。 - obataku
7个回答

25

使用 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.

3
我实际测试了一下,它并没有起作用。lastLine始终为空。 - user788171
4
好的,我会尽力进行翻译。请问需要翻译的内容是:"Funny, I tested it before posting. Does your test.txt have an extra blank line at the end?" 是吗? - derpface
6
在第一次调用seekg函数时,我也遇到了问题,直到我将该函数中的-1替换为-2,即在bool keepLooping = true;上面的fin.seekg(-1,ios_base::end);。请注意,这样做不会改变原意,只是让内容更通俗易懂。 - Lluís Alemany-Puig
1
@llualpu,我也遇到了同样的问题。但是你的建议确实有效。 - Vijay

4

最初它被设计用于读取最后一条syslog记录。鉴于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;
}

4

跳到结尾,然后向后阅读块,直到找到符合您对行的标准的内容。如果最后一个块没有以一行“结束”,则可能需要尝试向前扫描(假设在主动追加到文件中的情况下存在非常长的行)。


2
你如何准确地跳到结尾并倒序阅读代码块? - user788171
通过使用类似 istream::seekg(0, ios_base::end) 的方法,您可以在流中向前/向后移动。然后,您可以从那里使用 seekg 来进行定位。 - Yuushi

3

虽然derpface的答案绝对正确,但它常常返回意外结果。原因是在我的操作系统(Mac OSX 10.9.5)上,许多文本编辑器会以“结束行”字符来终止文件。

例如,当我打开vim,只输入单个字符“a”(没有回车),并保存时,该文件现在将包含以下内容(16进制):

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'文件。

编辑:行if((int)fin.tellg() <= 0){实际上会在文件太大(>2GB)时造成问题,因为tellg不仅返回从文件开始处的字符数(tellg()函数给出文件大小错误?)。最好分别测试文件开头fin.tellg()==tellgValueForStartOfFile和错误fin.tellg()==-1tellgValueForStartOfFile可能为0,但更好的确保方法可能是:

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

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

我也遇到了这个问题,因为我运行了uberwulu的代码,也得到了空白行。以下是我发现的情况。我使用以下.csv文件作为示例:

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

为了理解代码中的命令,请注意以下位置及其对应的字符。(位置,字符):...(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

我采用了Alexandros的解决方案并进行了一些改进

bool moveToStartOfLine(std::ifstream& fs)
{
    fs.seekg(-1, std::ios_base::cur);
    for(long i = fs.tellg(); i > 0; i--)
    {
        if(fs.peek() == '\n')
        {
            fs.get();
            return true;
        }
        fs.seekg(i, std::ios_base::beg);
    }
    return false;
}

std::string getLastLineInFile(std::ifstream& fs)
{
    // Go to the last character before EOF
    fs.seekg(-1, std::ios_base::end);
    if (!moveToStartOfLine(fs))
        return "";

    std::string lastline = "";
    getline(fs, lastline);
    return lastline;
}

int main()
{
    const std::string filename = "test.txt";
    std::ifstream fs;
    fs.open(filename.c_str(), std::fstream::in);
    if(!fs.is_open())
    {
        std::cout << "Could not open file" << std::endl;
        return -1;
    }

    std::cout << getLastLineInFile(fs) << std::endl;

    return 0;
}

如果文件包含空白的最后一行,则它会返回null。 - Shahriar.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接