发布于2026-06-30 阅读(0)
扫一扫,手机访问
文件读写乍看起来没什么门槛——不就是打开文件、读数据、处理完收工吗?但说出来你可能不信,就这条“最简单的路”,恰恰是无数Python开发者翻过车的地方。
很多人习惯用f.read()一把梭,把整个文件一股脑儿塞进内存。小文件这么写确实省事,代码也干净。可一旦文件膨胀到几百MB甚至几个GB,这招就行不通了——轻则程序卡顿、系统慢得像蜗牛,重则直接抛出MemoryError,连个招呼都不打,进程就被操作系统干掉。理解这背后的机制,掌握流式读取的方法,才是写出靠谱数据处理程序的根基。

with open('huge_log.txt', 'r') as f:
content = f.read()
# 如果 huge_log.txt 大小为 10 GB,服务器内存只有 8 GB,
# 程序会卡死或抛出 MemoryError
执行上面几行代码后,运气好的话你会看到:
MemoryError
运气不好的话,啥都看不到——操作系统直接掐掉了Python进程(这在Linux上叫OOM Killer介入)。即便文件大小刚好没超过可用内存,read()也会瞬间把大量数据填进RAM,导致系统里其他进程被“挤出去”,整个环境严重拖慢。
我们来看看read()到底干了什么。文件对象的read(size=-1)方法:
size或传入负值,它会读取文件的全部剩余内容直到文件结束(EOF)。所以处理大文件时,它要求的条件是:可用内存 ≥ 文件大小。但实际干活时,我们往往只需要一次看一行或一个数据块,完全没必要把整个文件同时放在内存里。
with open('huge.csv', 'r') as f:
lines = f.readlines() # 同样将所有行读入一个列表
readlines()一次性返回包含所有行的列表,每行是一个字符串。由于列表本身也有开销,内存占用比read()只大不小。
content = f.read()
for line in content.splitlines():
process(line)
这依然要把整个文件塞进内存,本质没有任何改变。
import pandas as pd
df = pd.read_csv('big_data.csv') # 默认一次性加载全部数据
很多高级库虽然提供了分块读取参数,但如果你忘记设置,照样会内存爆炸。
好在Python提供了多种优雅的流式处理方法,让内存占用只跟单次处理的数据块大小有关,而不随文件大小线性增长。
chunk_size = 4096 # 或 8192, 64*1024 等
with open('large_file.bin', 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
# 处理 chunk (bytes)
以文本模式打开的文件对象是一个可迭代对象,它会逐行产出str。
with open('huge_log.txt', 'r', encoding='utf-8') as f:
for line in f:
process(line)
readline()循环。注意:如果行特别长——比如单个JSON对象占一整行且高达数百MB——逐行迭代仍可能因为单行过大而内存暴涨。这时需要切换为分块读取并自行解析。
import fileinput
with fileinput.input(files=['log1.txt', 'log2.txt'], mode='r',
openhook=fileinput.hook_encoded('utf-8')) as f:
for line in f:
process(line)
fileinput同时串联多个文件,逐行遍历,自动关闭打开的文件。适合需要按行处理一系列大文件的场景,内存开销低。
如果需要随机访问或部分读取大文件,可以使用mmap模块将文件映射到虚拟内存空间,由操作系统按需加载页。
import mmap
with open('huge.dat', 'r+b') as f:
with mmap.mmap(f.fileno(), 0) as m:
# m 是一个类似字节数组的对象,支持切片
first_kb = m[:1024]
# 可以通过 find 等方法高效搜索
pos = m.find(b'ERROR')
mmap不会把整个文件读入物理内存,而是利用操作系统的内存页管理,只有实际访问的区域才会被加载。处理超大文件时性能极佳,随机访问也很方便。mmap。很多数据处理库原生支持流式读取:
pd.read_csv('data.csv', chunksize=10000) 返回一个迭代器,每次产出一个包含chunksize行的DataFrame。
for chunk in pd.read_csv('big.csv', chunksize=50000):
# 对 chunk 进行操作
ijson库进行增量解析,避免一次性加载整个JSON对象。xml.etree.ElementTree的iterparse支持流式处理。yield_per()分批获取查询结果。| 方法 | 内存占用 | 适用场景 | 复杂度 |
|---|---|---|---|
f.read() | 等于文件大小 | 小文件(< 几MB) | 极简 |
f.readlines() | 大于文件大小(+ 列表开销) | 小文件读所有行 | 简单 |
for line in f | 约等于最长行的字节数 | 基于行的文本处理 | 极简 |
分块 f.read(chunk_size) | 固定为 chunk_size | 任意二进制或需定界解析的文本 | 简单 |
mmap | 恒常低内存(页缓存) | 需要随机访问的巨大文件 | 中等 |
pandas chunksize | 单块大小 | 表格数据分析 | 简单 |
这里必须说清楚一点:流式读取在内存上确实占据绝对优势,但如果处理逻辑需要同时知道所有数据——比如全局排序、全数据集统计分析——单纯靠流式读取解决不了。这时可能需要外部排序、数据库或采样等其他技术。
# pip install memory_profiler
from memory_profiler import profile
@profile
def read_large():
with open('big.txt') as f:
return f.read()
运行这个脚本会输出每行代码的内存增量,read()的暴涨一目了然。
用os.path.getsize()获取文件大小,再决定是否使用流式读取:
import os
def smart_open(path, chunk_threshold=50*1024*1024): # 50 MB
size = os.path.getsize(path)
if size < chunk_threshold:
with open(path) as f:
return f.read()
else:
# 返回一个生成器,按行读取
with open(path) as f:
for line in f:
yield line
htop/free -m 观察进程内存。psutil库在代码中监控进程内存:
import psutil, os process = psutil.Process(os.getpid()) print(process.memory_info().rss) # 字节
for line in file 处理文本,这是Pythonic且安全的习惯。while chunk := f.read(CHUNK): 循环。chunksize、iterparse)。with语句,确保文件描述符不泄漏。“用read()读取大文件导致内存溢出”这个坑,从刚入门的新手到经验丰富的工程师都可能踩进去。最麻烦的地方在于:测试环境和少量数据下一切正常,换到真实环境、数据量一暴涨,瞬间崩盘。流式读取不是什么高深技巧,它是Python文件处理的基本素养。把它变成肌肉记忆——看到 f.read() 时,先问自己:“这个文件可能有多大?”——你就能避开无数头疼的问题,写出稳健、可扩展的数据处理程序。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8