sum(1 for _ in f)会卡死且易oom,因其本质是逐行迭代并计数,仍需python解析每行;真正高效的方法是分块读取二进制流并统计b'\n'字节。

用 sum(1 for _ in f) 会卡死,别试
读千万行文件时,用 len(f.readlines()) 或 sum(1 for _ in f) 看似简洁,实际会把整份内容加载进内存或反复触发 Python 解释器开销,极易 OOM 或耗时超 10 分钟。这不是代码写得不够“Pythonic”,是 I/O 模式错了。
真正快的路径只有一条:绕过 Python 的行解析逻辑,直接数换行符 \n —— 因为绝大多数文本文件的“行”就是靠它界定的。
- Linux/macOS 下优先用系统命令
wc -l,它用 C 实现、缓冲区优化极好,千万行通常在 1–3 秒内完成 - Windows 下可用
find /c "^"(需注意空行和最后一行无换行符时的偏差) - 跨平台且必须用 Python 时,用
open(..., 'rb')+ 手动扫描b'\n'字节,不 decode,不 split,不生成字符串
最稳的纯 Python 方案:分块读取二进制流
核心思路是避免逐行迭代,改成每次读几 MB 的字节块,用 bytes.count(b'\n') 累加。这个操作在 C 层实现,极快,且内存占用恒定(比如固定 8MB 缓冲区)。
示例函数:
def count_lines(filepath, chunk_size=8*1024*1024):
count = 0
with open(filepath, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
count += chunk.count(b'\n')
return count
- 若文件末尾无换行符,
count就是真实行数;若有,也正确(最后一行后那个\n不算新行) -
chunk_size设为 4–8MB 通常最优;太小增加系统调用次数,太大无意义 - 不要用
memoryview或mmap微优化——对行计数这种简单任务,反而引入复杂性和兼容性风险
用 wc -l 时要注意的三个坑
很多人直接 subprocess.run(['wc', '-l'], ...),但容易栽在这些地方:
- 路径含空格或中文时,
shell=True有安全风险,应传列表并确保filepath是绝对路径 -
wc -l对最后一行无换行符的文件,会少算 1 行(POSIX 标准行为),而用户常认为“有内容就算一行” - 某些嵌入式环境或容器里没
wc命令,需提前检查:shutil.which('wc')
补救办法:用 os.stat(filepath).st_size > 0 and not filepath.endswith(b'\n') 判断末尾是否缺换行符(需先读最后 1 字节),再手动 +1 —— 但多数场景下,接受 wc 的语义更省心。
为什么不用 pandas.read_csv(..., nrows=1) 或 dask?
它们设计目标是结构化数据解析,不是行数统计。哪怕只读头 1 行,pandas 仍会初始化整个引擎、推断 dtype、处理 quote/escape —— 开销远超必要。
-
dask.bag.from_sequence或dask.delayed分块统计?过度工程,启动调度器本身就要几百毫秒 - 想用多线程加速?磁盘 I/O 是瓶颈,不是 CPU,加线程反而因锁竞争变慢
- SSD 能提升速度,但算法不对,换再快的盘也救不了
readlines()
真正需要“千万行级快速统计”的人,往往在做日志预估、ETL 前校验或 pipeline 容量规划——这些场景要的是确定性、低延迟、低资源占用,而不是炫技式方案。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











