c++如何实现类似于tail -n的读取文件末尾N行功能_倒序查找【实战】

雨雪大大_6795

雨雪大大_6795

2026-03-28

834人浏览

原创

最可靠高效的方式是从文件末尾倒序扫描换行符:用binary模式读取缓冲区,在内存中倒查'\n',避免逐字节seek;需处理空文件、单行无换行、正在写入等边界情况。

c++如何实现类似于tail -n的读取文件末尾n行功能_倒序查找【实战】

用 fseek 从文件末尾倒着找换行符最可靠

直接读完整文件再 split('\n') 是最省事的写法,但大文件(比如几百 MB 的日志)会爆内存,而且完全没必要——tail -n 本质是定位最后 N 个 '\n' 的位置,不是解析全部内容。

核心思路:从文件末尾开始,逐字节往前扫描,数到第 N+1 个换行符时停下,之后读取到文件末尾就是目标行。注意:Windows 换行是 "\r\n",但绝大多数日志是 Unix 风格,只认 '\n' 就够用;若需跨平台,得跳过 '\r',但先按 '\n' 定位更稳。

  • 必须用 std::ios::binary 打开文件,否则 Windows 下文本模式会吞掉 '\r',导致偏移错乱
  • fseek(fp, -1, SEEK_END) 是起点,但第一次 fseek 到 SEEK_END 时,位置其实是文件末尾的 EOF 位置,再 -1 才是最后一个字节
  • 遇到文件为空或只有 1 行(无结尾 '\n')时,要单独处理:读不到换行符就从开头读起

别用 std::getline 从头读再丢弃前几行

有人想“先统计总行数,再跳过 total_lines - N 行”,这在 C++ 里极低效:每次 std::getline 都要动态分配字符串、找 '\n'、拷贝内容,且无法预知总行数就得扫两遍文件——第一遍数行数,第二遍读目标行。I/O 成本翻倍,还多一次内存分配。

真实场景中,日志文件往往持续追加,tail -n 100 要的是“当前末尾 100 行”,不是“历史全部行里的最后 100 行”。倒序扫描一次到位,复杂度 O(N + 文件末段长度),比扫全文件快一个数量级。

  • 用 std::ifstream::seekg() 倒着走比用 C 风格 fseek 更符合 C++ 习惯,但要注意:C++ 流的 seekg 在 binary 模式下行为和 fseek 一致
  • 不要用 std::string::reserve() 预估单行长度——行长不可控,不如让 std::getline 自动扩容
  • 如果 N 很大(比如 tail -n 10000),而文件本身没那么多行,得提前检测 EOF,避免无限循环

处理边界情况:空文件、单行无换行、最后一行没 '\n'

真实日志文件常出现“正在写入中”的情况:最后一行还没写完换行符。这时倒序扫描会停在倒数第二个 '\n',漏掉最后一行。正确做法是:找到第 N 个 '\n' 后,再检查当前位置是否已到文件开头;如果不是,说明还有至少一行未被计入,应把从该位置到 EOF 的全部内容作为第 1 行加入结果。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
  • 文件大小为 0 → 直接返回空 vector
  • 文件大小为 1,内容是 '\n' → 算作 1 行空行;内容不是 '\n' → 算作 1 行无换行的行
  • 扫描过程中 seekg 返回 false(或 fseek 返回 -1)→ 已到文件头,停止计数,剩余部分即首行
  • 用 std::vector<:string> 存结果,倒序插入(insert(begin(), line))比正序 push 再 reverse 更清晰

性能关键:用 std::vector 缓存小段内容,避免反复 seekg + get()

逐字节 seekg + get() 在机械盘上很慢,SSD 上也不理想。更优做法是:先读最后 8KB(或自定义缓冲区大小)进 std::vector,然后在内存里倒着扫描 '\n'。如果缓冲区不够,再往前读一段,直到凑够 N+1 个换行符或到文件头。

这个策略把随机 I/O 变成顺序 I/O,对 HDD 提升明显,对 SSD 也能减少系统调用次数。缓冲区大小设为 4096 或 8192 足够覆盖绝大多数短行日志;若日志行普遍很长(如 JSON 日志),可设为 64KB,但别盲目堆大——内存占用和缓存局部性要权衡。

  • 用 file.seekg(0, std::ios::end); auto size = file.tellg(); 获取文件大小,判断是否小于缓冲区,避免过度读取
  • 读取时用 file.read(buffer.data(), buf_size),比循环 get() 快得多
  • 扫描 buffer 时用指针或反向迭代器(rbegin()),别用 at() 做边界检查,影响性能

倒序扫描的难点不在算法,而在对文件末尾状态的精确建模:有没有换行、是否正在写入、跨平台换行差异。这些细节不处理好,tail -n 就会在某些文件上少一行或多一行,而这种 bug 很难复现。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

2023.08.07

2335

5

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.23

3842

1

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

2023.10.13

1276

3

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

2025.09.10

4459

7

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

7519

3

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.03

1938

5

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.04

3184

5

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

2023.10.24

6384

49

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

2023.11.24

6549

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习