C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++获取日志文件最后10行

三种在C++中高效获取日志文件最后10行的方法

作者:weixin_pk138132

C++编程中,你经常需要处理文件,尤其是日志文件,一个非常常见的任务是:我不想看整个10GB的日志文件,我只想看最后 10 行,看看最近发生了什么,所以本文给大家介绍了三种在C++中高效获取日志文件最后10行的方法,需要的朋友可以参考下

在C++编程中,你经常需要处理文件,尤其是日志文件。一个非常常见的任务是:“我不想看整个10GB的日志文件,我只想看最后 10 行,看看最近发生了什么。”

这就像 Linux/macOS 上的 tail -n 10 命令。

一个简单的比喻:“读一本厚书的最后一章”

在本教程中,你将学会:

前置知识说明 (100% 自洽):

准备工作:创建一个测试文件testlog.txt

在运行代码前,请在你的 .cpp 文件相同的目录下,创建一个名为 testlog.txt 的文件,并填入以下内容(确保最后一行有换行):

Line 1: The quick brown fox
Line 2: jumps over
Line 3: the lazy dog.
Line 4: ---
Line 5: C++ File I/O
Line 6: is powerful.
Line 7: ---
Line 8: Testing line 8.
Line 9: Testing line 9.
Line 10: Testing line 10.
Line 11: Testing line 11.
Line 12: Testing line 12.
Line 13: This is the final line.

第一部分:方法 1 (“天真”法) —— 读取所有行

逻辑: 把文件的每一行都读入一个 vector<string>,然后只打印这个 vector 的最后 10 个元素。

naive_tail.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

void printLast10_Naive(const string& filename) {
    ifstream file(filename);
    if (!file.is_open()) {
        cerr << "错误: 无法打开文件 " << filename << endl;
        return;
    }

    vector<string> allLines;
    string line;
    
    // 1. “天真”地读取 *所有* 行
    while (getline(file, line)) {
        allLines.push_back(line);
    }
    file.close();

    // 2. 计算从哪里开始打印
    int totalLines = allLines.size();
    int start_index = 0;
    if (totalLines > 10) {
        start_index = totalLines - 10;
    }

    // 3. 打印最后 10 (或更少) 行
    cout << "--- 方法 1 (Naive) ---" << endl;
    for (int i = start_index; i < totalLines; ++i) {
        cout << allLines[i] << endl;
    }
}

int main() {
    printLast10_Naive("testlog.txt");
    return 0;
}

第二部分:方法 2 (“折中”法) —— 循环缓冲区

逻辑: 我们只保留一个固定大小(10)的“缓冲区”(使用 deque)。从头到尾读取文件,每读一行,就把它塞进缓冲区,如果缓冲区“满了”(超过10),就从前面挤掉”最旧的那一行。

circular_buffer.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <deque> // 需要双端队列
using namespace std;

void printLast10_Circular(const string& filename, int N = 10) {
    ifstream file(filename);
    if (!file.is_open()) {
        cerr << "错误: 无法打开文件 " << filename << endl;
        return;
    }

    deque<string> buffer;
    string line;

    // 1. 仍然读取 *所有* 行
    while (getline(file, line)) {
        // 2. 添加到“队尾”
        buffer.push_back(line);
        
        // 3. 如果缓冲区“超载”,从“队首”挤掉
        if (buffer.size() > N) {
            buffer.pop_front();
        }
    }
    file.close();

    // 4. 打印缓冲区中剩下的 N 行
    cout << "--- 方法 2 (Circular Buffer) ---" << endl;
    for (const string& s : buffer) {
        cout << s << endl;
    }
}

int main() {
    printLast10_Circular("testlog.txt");
    return 0;
}

第三部分:方法 3 (“专业”法) ——seekg反向读取

逻辑: 像“tail 命令”一样,直接跳到文件末尾,然后一个字节一个字节地往前“挪”,同时**“数”**换行符 \n。当我们数到 10 个换行符时,我们就找到了第 10 行的开头。

seekg_pro.cpp (推荐的方式)

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

void printLast10_Pro(const string& filename, int N = 10) {
    ifstream file(filename);
    if (!file.is_open()) {
        cerr << "错误: 无法打开文件 " << filename << endl;
        return;
    }

    // 1. 跳转到文件末尾
    //    (ios::ate 模式可以打开文件并立即定位到末尾)
    //    或者使用 seekg:
    file.seekg(0, ios::end); 

    // 2. 获取当前位置(即文件总大小)
    long long pos = file.tellg();
    
    // 如果文件为空
    if (pos == 0) {
        cout << "文件为空。" << endl;
        return;
    }
    
    int newlineCount = 0;
    string lineBuffer; // 用于读取最后的残行

    // 3. “行内预警”:我们从 *最后一个字符* 开始往前“跳”
    // (pos 是文件大小,最后一个字符的索引是 pos - 1)
    for (long long i = pos - 1; i >= 0; i--) {
        file.seekg(i); // “跳”到第 i 个字节
        
        char c = file.get(); // 读取那 1 个字节
        
        if (c == '\n') {
            newlineCount++;
        }
        
        // 4. “刹车”:当我们找到 N 个换行符时
        // (注意:GFG的例子是 == N,但 >= N 更健壮)
        if (newlineCount >= N) {
            // “行内预警”:我们需要跳到 *这个换行符之后* 的位置
            file.seekg(i + 1); 
            break; // 停止“回溯”
        }
    }
    
    // 5. 如果文件行数不足 N,我们最终会跳到开头
    if (newlineCount < N) {
        file.seekg(0); // 重置到文件开头
    }

    // 6. 现在,从我们“停下”的位置,*顺序* 读到文件末尾
    cout << "--- 方法 3 (Seek from End) ---" << endl;
    string line;
    while (getline(file, line)) {
        cout << line << endl;
    }

    file.close();
}

int main() {
    printLast10_Pro("testlog.txt");
    return 0;
}

“手把手”终端模拟 (所有方法):

PS C:\MyCode> g++ ... # 编译所有 .cpp 文件
PS C:\MyCode> .\naive_tail.exe
--- 方法 1 (Naive) ---
Line 4: ---
Line 5: C++ File I/O
Line 6: is powerful.
Line 7: ---
Line 8: Testing line 8.
Line 9: Testing line 9.
Line 10: Testing line 10.
Line 11: Testing line 11.
Line 12: Testing line 12.
Line 13: This is the final line.

PS C:\MyCode> .\circular_buffer.exe
--- 方法 2 (Circular Buffer) ---
Line 4: ---
... (输出同上) ...
Line 13: This is the final line.

PS C:\MyCode> .\seekg_pro.exe
--- 方法 3 (Seek from End) ---
Line 4: ---
... (输出同上) ...
Line 13: This is the final line.

顿悟时刻: 三种方法结果相同,但效率(尤其是内存和I/O)天差地别seekg 是处理大文件的“专业”选择。

第四部分:“X光透 视”——亲眼目睹“反向搜寻”

让我们用“X光眼镜”(调试器)来观察 seekg_pro.cpp 是如何工作的。

“X光”实战(基于seekg_pro.cpp)

设置断点:

启动“子弹时间”(F5):

第一次“冻结” (i = 249, 假设):

继续执行 (F5):

(程序继续) file.seekg(61) 将指针设置到“第4行”的开头,while (getline(...)) 开始顺序打印,直到文件末尾。

动手试试!(终极挑战:你的“可配置tail”)

现在,你来当一次“工具开发者”。

任务:

  1. 复制本教程“方法 2 (循环缓冲区)”的代码(printLast10_Circular)。
  2. 修改这个函数,使其能够返回一个 vector<string>,而不是 void(打印)。
  3. main 函数中,调用这个新函数(比如 vector<string> lastLines = getLastNLines("testlog.txt", 5);),并自己遍历打印这个返回的 vector
  4. (进阶) 复制本教程“方法 3 (专业 seekg)”的代码,并同样将其修改为返回 vector<string>,而不是 void(打印)。(提示:在 file.seekg(pos); 之后,你需要使用 getline 循环把剩余的行读入一个新的 vector 并返回)。

flexible_tail.cpp (你的 TODO - 挑战方法 2):

#include <iostream>
#include <fstream>
#include <string>
#include <deque>
#include <vector>
using namespace std;

// --- TODO 1 & 2: 修改函数,使其返回 vector<string> ---
vector<string> getLastNLines_Circular(const string& filename, int N = 10) {
    ifstream file(filename);
    deque<string> buffer;
    
    // (如果打开失败,返回一个空 vector)
    if (!file.is_open()) {
        cerr << "错误: 无法打开文件 " << filename << endl;
        return vector<string>(); 
    }
    
    string line;
    while (getline(file, line)) {
        buffer.push_back(line);
        if (buffer.size() > N) {
            buffer.pop_front();
        }
    }
    file.close();

    // --- TODO 2: 将 deque 转换为 vector 并返回 ---
    // (提示:vector 有一个构造函数可以直接接收两个迭代器)
    // return vector<string>(buffer.begin(), buffer.end());
}

int main() {
    // --- TODO 3: 调用新函数并打印 ---
    cout << "--- 测试 getLastNLines (N=5) ---" << endl;
    
    // vector<string> last5Lines = getLastNLines_Circular("testlog.txt", 5);
    
    // for (const string& s : last5Lines) {
    //     cout << s << endl;
    // }
    
    return 0;
}

这个挑战让你把“打印”逻辑和“数据获取”逻辑分离开,这是更健壮的函数设计。如果你能进一步挑战并修改 seekg 版本,你就能完全掌握C++中高效文件读取的精髓!

以上就是三种在C++中高效获取日志文件最后10行的方法的详细内容,更多关于C++获取日志文件最后10行的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文