直接用open()读大文件会失败,因其一次性加载全部内容至内存引发MemoryError;断点续传须基于字节偏移量(非行号),必须用'rb'模式确保seek()/tell()准确,状态需原子写入.offset文件并校验文件一致性。

为什么直接用 open() 读大文件会失败
因为一次性加载整个文件进内存,MemoryError 很快就来了。更麻烦的是,如果读取中途网络中断或程序崩溃,从头开始重读既耗时又浪费带宽。断点续传不是靠“记住读到哪行”,而是靠“记住读到哪个字节偏移量”——这是底层文件操作的可靠依据。
关键点:必须用二进制模式('rb')打开文件,才能准确控制字节位置;文本模式会因换行符编码、BOM 等干扰 seek() 的准确性。
- 用
os.path.getsize()获取总大小,判断是否已读完 - 用
f.seek(offset)跳转到上次中断位置,offset必须是整数,且 ≤ 文件大小 - 每次读取后记录当前
f.tell()值,存到本地状态文件(如resume.offset)
seek() 和 tell() 在大文件中怎么用才不翻车
seek() 在大文件里不是万能的。比如在 Windows 上用 Python 3.7+ 读取超过 2GB 的文件时,如果用 seek() 跳转到高位偏移(比如 3GB 处),可能触发 OSError: [Errno 22] Invalid argument —— 这是因为底层 C 库对 off_t 类型的限制。解决方案是确保 Python 使用 64 位 off_t(通常 64 位系统默认支持),但更稳妥的做法是:始终用 os.SEEK_SET 模式,并避免在未关闭文件前多次 seek() 到极大值。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
- 打开文件后立即调用
f.seek(0, os.SEEK_END)再f.tell()获取真实大小,比os.path.getsize()更可靠(尤其对设备文件或 NFS) - 每次
read()后立刻调用f.tell(),不要依赖累计计算的 offset,浮点误差或编码边界会导致错位 - 写入 offset 状态时,用
str(offset).encode('ascii')存,避免编码问题;读取时用int(state_data.strip())
如何安全保存和恢复断点位置
状态文件本身可能写一半就崩溃,导致下次读出错。不能只写一个纯数字文本文件就完事。推荐用原子写入:先写到临时文件(如 resume.offset.tmp),再 os.replace() 替换原文件。这样即使中断,旧状态仍完整。
立即学习“Python免费学习笔记(深入)”;
- 恢复时先尝试读
resume.offset,若不存在或内容非数字,从 0 开始 - 读取状态后,用
os.stat()校验原始文件是否被修改过(比较st_mtime或st_size),防止 resume 文件对应旧版本文件 - 不要把 offset 存进数据库或配置中心——本地磁盘延迟低、原子性强,更适合这种高频小数据
实际读取循环里要注意什么
读大文件不是简单 while True + read(8192)。你得处理最后一块不足缓冲区大小的情况,还要区分“读到 EOF”和“读到 0 字节但文件没结束”(比如某些网络文件系统)。真正的终止条件是 len(chunk) == 0,而不是 chunk == b'' —— 虽然效果一样,但语义更清晰。
- 缓冲区大小建议设为 64KB(
65536):太小增加系统调用次数,太大占用内存且无明显收益 - 每次读取后立即更新状态文件,哪怕只读了 1KB —— 宁可多写几次,也不能丢进度
- 如果读取目标是 HTTP 分块传输,记得检查响应头
Content-Range是否匹配预期 offset,否则可能是服务端重置了连接

















