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 行打印一次日志。用户体验会好很多。

说白了,大文件处理的核心就一句话:别一次性全读进来。只要记住这个原则,剩下的都是技术细节。

大文件读取策略 大文件(> 内存) 逐行读取 for line in f 分块读取 f.read(chunk_size) 迭代器封装 yield chunk 内存占用:一行 内存占用:可控 内存占用:可控
可可精品资料库