4、读取大文件:逐行读取、分块读取、使用迭代器
处理大文件,说白了就是内存不够用的问题。
我记得刚入行那会儿,接手一个日志分析任务。文件有 5 个 G,我直接用 read() 一把梭。结果呢?内存直接爆了,电脑卡死,我差点被老板骂哭。从那以后,我学乖了——大文件必须「分而治之」。
4.1 逐行读取:最优雅的方式
Python 里处理大文件,最推荐的就是逐行读取。你想想看,一行数据能有多大?一般也就几百字节。哪怕文件有 10 个 G,内存里始终只占一行,稳得很。
核心代码其实就一句话:
with open('huge_file.log', 'r', encoding='utf-8') as f:
for line in f:
# 处理每一行
process(line)
这里有个细节我特别想强调:for line in f 这个写法,Python 内部其实已经帮我们做了缓冲。它不会一次读一行,而是读一个 8KB 的块到内存,然后逐条 yield 出来。既高效又省心。
for line in f 而不是 readlines()。后者会把所有行都加载到内存,大文件直接 GG。
4.2 分块读取:更精细的控制
逐行读取虽然好,但有些场景它搞不定。比如读取二进制文件,或者你需要按固定大小切分数据。这时候就得用分块读取了。
我曾经处理过一个网络抓包文件,每 1024 字节是一个完整的数据包。逐行读取?不存在的。我直接用 read(size) 按块读:
def read_in_chunks(file_path, chunk_size=1024):
with open(file_path, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
# 处理这个数据块
process_chunk(chunk)
分块读取的好处是你可以精确控制内存占用。比如服务器上内存吃紧,你可以把 chunk_size 设成 256 字节。反之,如果内存充裕,设成 64MB 能大幅提升吞吐量。
\n 切分,结果把一个完整的行切成了两半。后来我改用 io.BufferedReader 配合 readline(),才彻底解决这个问题。记住:分块读取二进制文件没问题,但处理文本文件时,一定要考虑行边界。
4.3 使用迭代器:Pythonic 的终极方案
逐行读取和分块读取,本质上都是迭代。Python 的迭代器模式,就是为这种场景量身定做的。
我特别喜欢把读取逻辑封装成一个生成器函数。这样调用方只需要 for 循环,完全不用关心底层是怎么读的:
def read_large_file(file_path, chunk_size=8192):
"""一个通用的、可迭代的大文件读取器"""
with open(file_path, 'r', encoding='utf-8') as f:
while True:
data = f.read(chunk_size)
if not data:
break
yield data
# 使用示例
for chunk in read_large_file('big_data.csv'):
# 处理每个数据块
analyze(chunk)
你想想看,这样做的好处是什么?
- 延迟加载:数据是按需产生的,不会一次性占用内存
- 可组合:你可以把多个迭代器串起来,形成处理管道
- 可复用:这个生成器函数可以在任何项目里直接拿来用
嗯,这里要注意一点:迭代器是一次性的。如果你需要多次遍历同一个文件,要么重新打开,要么把数据缓存到列表里(但这就违背了大文件处理的初衷)。
4.4 三种方式的对比
我把这三种方式整理了一下,方便你根据场景选择:
| 方式 | 适用场景 | 内存占用 | 代码复杂度 |
|---|---|---|---|
| 逐行读取 | 文本文件,每行独立 | 极低(一行) | 低 |
| 分块读取 | 二进制文件,固定大小数据包 | 可控(块大小) | 中 |
| 迭代器封装 | 需要复用、组合读取逻辑 | 可控 | 中高 |
4.5 实战中的避坑指南
最后分享几个我踩过的坑:
- 编码问题:大文件经常混着各种编码。我习惯用
chardet库先检测编码,再打开文件。别硬编码utf-8,否则遇到 GBK 文件直接崩。 - 文件句柄泄漏:用
with语句是基本素养。我见过有人手动open()忘了close(),结果程序跑着跑着报「Too many open files」。 - 进度反馈:处理大文件时,用户最怕的就是「卡死了」。我一般会加个进度条,比如用
tqdm库,或者每隔 N 行打印一次日志。用户体验会好很多。
说白了,大文件处理的核心就一句话:别一次性全读进来。只要记住这个原则,剩下的都是技术细节。