读取文件的最后一行

4

我有一个大文件,需要从中获取最后一行(仅使用\n作为行分隔符)。
我需要在iOS设备上完成此操作,因此不能占用太多内存或CPU时间(例如读取整个文件)。
如何在Objective-C,c++或c++11中实现此功能?

4个回答

5
概念上,您需要打开文件并将光标定位到末尾减去N个字节(例如80个字节)。然后读取它并查找\n。如果找不到,则向前移动N个字节并在该N个字节集上再次尝试,直到找到\n为止。
至于具体的调用,这只是查找如何打开文件,在其中搜索并读取数据的问题。应该相当简单。但我认为上述内容是您需要做的,并选择一个不太大的N的大小。

N = 80 是否比 N = 1 更有优势? - Daniel
是的,寻址可能很慢,而同时读取多个文件时几乎与读取一个文件的速度相同。 - Mooing Duck

3

我在生产代码中拥有这个功能。思路是通过寻找和读取方式试图读取最后一行。请看一下。

bool readLastLine(std::string const& filename, std::string& lastLine)
{
    std::ifstream in(filename.c_str(),std::ifstream::binary);
    if(!in) return false;
    in.seekg(0, std::ifstream::end);
    const std::streamoff len = in.tellg();
    //empty file
    if(len == 0)
    {
        lastLine = "";
        return true;
    }
    int buf_size = 128;
    std::vector<char> buf;
    while(in)
    {   
        if(buf_size > len)
        {
            buf_size = len;
        }
        buf.resize(buf_size);
        in.seekg(0 - buf_size, std::ifstream::end);
        in.read(&buf[0],buf_size);
        //all content is in the buffer or we already have the complete last line
        if(len == buf_size || std::count(buf.begin(), buf.end(), '\n') > 1)
        {
            break;
        }
        //try enlarge the buffer
        buf_size *= 2;
    }
    //find the second line seperator from the end if any
    auto i = std::find(++buf.rbegin(),buf.rend(), '\n');
    lastLine.assign(i == buf.rend() ?  buf.begin() : buf.begin() + std::distance(i, buf.rend()), buf.begin() + buf_size);
    return true;
}

你在这里遇到了一个问题,即你多次读取结尾,并在其中多次查找换行符,但这是没有必要的 - 你已经读取了它并且知道它没有新的行。 - Daniel
@Dani,你可以进行任何优化。 对于大多数情况,只要最后一行的长度小于128,我们仅读取最后一行。 我可以将默认缓冲区大小更改为512。 在我的产品中它运作良好。 - BruceAdi

2

@Nerdtron的回答对我来说似乎是最合适的,如果您无法控制文件格式,但是...

如果您可以控制文件格式,则可以使用O(1)复杂度来完成此操作。当您将数据写入文件时,只需将上一行开头的偏移量写入文件开头的(常量)偏移量即可。当您想要读取它时,读取此偏移量,并转到指定的偏移量。


0

我想到了这个方法,试图在Bruce的基础上进行改进,好处是缓冲区不需要重新调整大小,只需不断读取相同大小的字符块,直到离文件尾越来越远:

std::string lastLine(std::ifstream &file)
{
    if (!file.good()) throw exception("Bad stream on input");

    const size_t bufSize = 80; // because why not? tweak if need to
    char buf[bufSize];
    string line;

    int seek, nloff;
    // iterate over multiples of bufSize while file ok
    for (size_t n = 1; file; ++n)
    {
        // next seek position will be a multiple of bufSize
        seek = -static_cast<int>(n * bufSize);
        file.seekg(seek, file.end);
        // read "bufSize" bytes into buffer
        file.read(buf, bufSize);

        // in case no newline found, seek past eof
        nloff = -seek;
        // find offset of last newline in buffer
        for (size_t i = 0; i < bufSize; ++i)
        {
            if (buf[i] == '\n') nloff = i;
        }
        seek += nloff + 1; // new seek position is one character after found newline
        if (seek >= 0) continue; // just kidding about the "past eof" part ;)

        // seek to after found newline and get line
        file.seekg(seek, file.end);
        getline(file, line);
        if (!line.empty()) break; // have result, break and return
    }

    if (file.good()) return line;
    else return string();
}

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