平滑记录断点需主动维护行号、捕获异常并持久化断点:每次readLine()成功后递增行号并立即保存;用try-catch-finally确保断点落地;断点文件存纯文本整数,首次运行视为从第1行开始;不依赖try-with-resources保断点。

用缓冲流读取本地文件时,若中途发生异常(比如IO中断、权限变化或磁盘故障),要平滑记录“断在哪一行”,关键不是靠缓冲流本身——它不自带断点能力——而是靠主动维护行号 + 异常捕获 + 外部持久化断点三者配合。BufferedReader 的 readLine() 是按行推进的,只要你在每次成功读取后更新计数,并在异常发生时立刻把当前行号写入一个轻量文件,就能实现“中断即落点”。
明确行号归属逻辑
行号必须与实际读取动作严格同步,不能依赖循环变量 i 或枚举索引,因为 BufferedReader 内部缓冲可能预读多行。正确做法是:每调用一次 readLine() 并得到非 null 结果,就认为这一行已“确认处理”,此时递增行计数并可选做业务解析;只有在这之后才更新断点。
- 不要在
for (int i = 1; ...)中用 i 当行号——i 可能跳过空行或因异常未执行到 - 不要在
readLine()返回前写断点——那会把未处理的行误标为“已处理” - 推荐模式:
line = br.readLine(); if (line != null) { process(line); lineNum++; saveCheckpoint(lineNum); }
用 try-catch-finally 确保断点落地
异常可能发生在解析逻辑里(如 JSON 格式错误)、IO 层(如磁盘满)、甚至业务校验中。无论哪一层崩了,只要还没退出当前读取循环,就要抓住 finally 块把最新行号存下来。注意:saveCheckpoint() 自身也应具备容错性,比如写临时文件再原子替换,避免断点文件损坏。
- 在 while 循环内包裹单行处理逻辑,用 try-catch 捕获业务异常
- 在该 try 块外加一个 finally,仅用于调用
saveCheckpoint(lineNum) - 如果整个 while 被 IOException 中断(如连接断开),则在最外层 catch 中再调用一次 saveCheckpoint
断点文件设计要轻量且可靠
断点只需存一个整数,推荐纯文本格式(如 checkpoint.txt 写入 127),不用 JSON 或 XML。读取时用 Integer.parseInt(Files.readString(Paths.get("checkpoint.txt"))),写入时用 Files.writeString(Paths.get("checkpoint.txt"), String.valueOf(lineNum)),全程避开流手动管理,减少出错环节。
- 首次运行时 checkpoint 文件不存在,视为从第 1 行开始
- 恢复读取时,用
skipNLines(lineNum)或重开流后循环readLine()跳过前 N 行(BufferedReader 不支持随机 seek) - 避免用时间戳或哈希值替代行号——行号是唯一可逆、可对齐、可验证的续点依据
结合 try-with-resources 但不依赖它保断点
try-with-resources 能自动关流,但它在异常传播时会先执行 close() 再抛异常,无法保证断点写入时机。所以正确结构是:外层 try-with-resources 包裹 BufferedReader,内部 while 循环自己管断点;或者放弃 try-with-resources,改用显式 try-finally 关流,把断点保存和流关闭放在同一个 finally 块里。
- 推荐组合:try-with-resources 管流生命周期 + 内部 finally 管断点写入
- 不推荐:把
saveCheckpoint()放进 try-with-resources 的资源声明里——它不是 AutoCloseable - 更稳做法:断点写入操作单独封装,失败时只打日志,不阻断主流程

















