← 返回题目列表

Python 如何高效读取大文件?read、readline、readlines 和文件迭代有什么区别?

高频 中等 第 6 / 27 题 更新于 2026/07/27
大文件文件迭代内存优化

简化版

读取大文件不要直接 read()readlines() 一次性加载全部内容,优先使用文件对象迭代或按块读取。按行处理用 for line in f,按固定大小处理用 f.read(chunk_size) 循环,这样内存占用更稳定。

详细版

几种读取方式的区别:

方法行为适合场景风险
read()一次读取全部或指定字符/字节数小文件、明确大小的内容不传大小时可能占满内存
readline()每次读取一行手动控制逐行读取写循环稍繁琐
readlines()一次读取所有行组成列表小文本文件大文件会占大量内存
for line in f惰性逐行迭代日志、CSV、逐行解析每行特别长时仍需小心
read(chunk)按块读取拷贝文件、上传下载、二进制流需要自己处理循环

推荐写法:

with open("app.log", "r", encoding="utf-8") as f:
    for line in f:
        if "ERROR" in line:
            print(line.rstrip("\n"))

按块读取:

chunk_size = 1024 * 1024

with open("big.bin", "rb") as f:
    while chunk := f.read(chunk_size):
        process(chunk)

面试回答要突出:大文件处理的关键是流式处理,不把整个文件一次性放进内存。

完整版教学

一、大文件读取的核心矛盾是内存

假设一个日志文件有 20GB,而服务器只有 8GB 内存,如果直接:

content = open("app.log", "r", encoding="utf-8").read()

程序需要尝试把整个文件内容变成一个字符串对象放进内存,轻则变慢,重则进程被系统杀掉。大文件处理的思路不是“更快地全部读入”,而是“边读边处理”。

文件对象天然支持迭代,逐行读取时不会一次性保存所有行:

with open("app.log", "r", encoding="utf-8") as f:
    for line in f:
        handle(line)

这类写法特别适合日志分析、数据清洗、逐行校验。

二、read 和 readlines 为什么容易出问题

read() 不带参数时读取全部内容:

text = f.read()

对小配置文件没问题,但对未知大小的用户上传文件、日志文件、导出文件很危险。

readlines() 会把所有行读成一个列表:

lines = f.readlines()

它不仅保存文本内容,还要保存列表结构和每个字符串对象引用,内存开销通常比文件本身更大。很多初学者以为“按行”就省内存,但 readlines() 并不是流式逐行处理,它是一次性读出所有行。

三、文件迭代为什么是推荐方式

文件对象实现了迭代协议。写:

for line in f:
    ...

Python 会按需读取下一行。代码简单,内存稳定,可读性也好。

处理行尾时,不建议粗暴使用 strip(),因为它会去掉两端所有空白,包括有效的空格和制表符。只想去掉换行时可以用:

line = line.rstrip("\n")

如果文件可能来自 Windows,也可以:

line = line.rstrip("\r\n")

四、什么时候用按块读取

逐行读取适合文本。二进制文件通常没有“行”的概念,应该按块读取。

def copy_file(src, dst):
    with open(src, "rb") as r, open(dst, "wb") as w:
        while chunk := r.read(1024 * 1024):
            w.write(chunk)

这种方式适合:

  • 大文件复制;
  • 文件上传下载;
  • 分片计算哈希;
  • 处理音视频、图片、压缩包。

块大小不是越大越好。太小会增加系统调用次数,太大又浪费内存。常见可以从 64KB、1MB、4MB 这类量级开始,根据场景压测。

五、逐行读取也不是万能的

如果文件里某一行特别长,比如一行就是几百 MB 的 JSON,for line in f 依然会一次读入这一整行。此时应该改用流式解析器、按块解析协议,或者从数据格式上避免“超长单行”。

另外,文本解码也可能成为瓶颈。如果只是搬运文件,不需要理解文本内容,用二进制按块复制更稳。

六、面试中的加分回答

可以把答案组织成三个层次:

  1. 小文件可以 read(),简单直接;
  2. 大文本文件用文件对象迭代,逐行处理;
  3. 大二进制文件或流式传输用 read(chunk_size) 按块处理。

再补一句:如果是复制文件,生产中也可以考虑 shutil.copyfileobj() 等标准库能力,少写低层循环。

七、常见误区与追问

记忆钩子:大文件读取先问“内存能不能装下”。装不下就不要一次性读,按行、按块或流式处理,把峰值内存控制住。

  • 误区:read() 最简单,所以读大文件也优先用它。 read() 会尝试把整个文件读入内存,10GB 文件可能直接把进程内存打爆;大文件要流式读取。
  • 追问:逐行读取为什么省内存? 文件对象本身可迭代,每次只取下一行,内存主要保存当前行和处理状态,而不是整个文件内容。
  • 误区:readlines() 和逐行 for line in f 差不多。 readlines() 会把所有行组成列表放进内存;for line in f 是按需迭代,内存占用低很多。
  • 追问:什么时候按块读取比逐行更合适? 二进制文件、没有明确行分隔的大文件、需要传输或计算哈希时,固定块读取更自然,例如每次 64KB 或 1MB。
  • 误区:块越大性能一定越好。 块太小系统调用多,块太大内存峰值高且缓存不友好;实际要结合磁盘、网络、处理逻辑测试,不能只背一个固定大小。
  • 追问:如果一行本身就非常大怎么办? 逐行读取也可能吃很多内存,这时要考虑按块解析、流式解析器、分片协议或先约束输入格式。

八、加强记忆

大文件读取的原则是“不要一次性吃完”。read()readlines() 适合小文件,未知大小的文本用 for line in f,二进制或传输场景用 read(chunk_size) 循环。真正的高效不是某个 API 名字,而是让内存占用随文件大小保持稳定。