Python 如何高效读取大文件?read、readline、readlines 和文件迭代有什么区别?
简化版
读取大文件不要直接 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 依然会一次读入这一整行。此时应该改用流式解析器、按块解析协议,或者从数据格式上避免“超长单行”。
另外,文本解码也可能成为瓶颈。如果只是搬运文件,不需要理解文本内容,用二进制按块复制更稳。
六、面试中的加分回答
可以把答案组织成三个层次:
- 小文件可以
read(),简单直接; - 大文本文件用文件对象迭代,逐行处理;
- 大二进制文件或流式传输用
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 名字,而是让内存占用随文件大小保持稳定。