如何在Python中以低成本获取大文件的行数

1296 投票
44 回答
1119043 浏览
提问于 2025-04-15 11:29

我怎样才能以最省内存和最省时间的方式来统计一个大文件的行数呢?

def file_len(filename):
    with open(filename) as f:
        for i, _ in enumerate(f):
            pass
    return i + 1

44 个回答

230

我认为使用内存映射文件会是最快的解决方案。我尝试了四个函数:OP发布的函数(opcount);一个简单的逐行读取文件的函数(simplecount);使用内存映射文件的读取函数(mapcount);还有Mykola Kharechko提供的缓冲区读取解决方案(bufcount)。

我每个函数都运行了五次,并计算了处理一个120万行文本文件的平均运行时间。

我的测试环境是:Windows XP,Python 2.5,2GB内存,2GHz的AMD处理器。

以下是我的结果:

mapcount : 0.465599966049
simplecount : 0.756399965286
bufcount : 0.546800041199
opcount : 0.718600034714

对于Python 2.6的结果:

mapcount : 0.471799945831
simplecount : 0.634400033951
bufcount : 0.468800067902
opcount : 0.602999973297

所以缓冲区读取策略似乎是Windows/Python 2.6下最快的。

以下是代码:

from __future__ import with_statement
import time
import mmap
import random
from collections import defaultdict

def mapcount(filename):
    with open(filename, "r+") as f:
        buf = mmap.mmap(f.fileno(), 0)
        lines = 0
        readline = buf.readline
        while readline():
            lines += 1
        return lines

def simplecount(filename):
    lines = 0
    for line in open(filename):
        lines += 1
    return lines

def bufcount(filename):
    f = open(filename)
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('\n')
        buf = read_f(buf_size)

    return lines

def opcount(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1


counts = defaultdict(list)

for i in range(5):
    for func in [mapcount, simplecount, bufcount, opcount]:
        start_time = time.time()
        assert func("big_file.txt") == 1209138
        counts[func].append(time.time() - start_time)

for key, vals in counts.items():
    print key.__name__, ":", sum(vals) / float(len(vals))
807

这一行代码比提问者的 for 循环要快(虽然不是最快的),而且写得很简洁:

num_lines = sum(1 for _ in open('myfile.txt'))

你还可以通过使用 rbU 模式来提高速度(和稳定性),并把它放在 with 语句块里,这样可以自动关闭文件:

with open("myfile.txt", "rbU") as f:
    num_lines = sum(1 for _ in f)

注意: 从 Python 3.3 及以上版本开始,rbU 模式已经不推荐使用,所以我们应该用 rb 来代替 rbU(而且在 Python 3.11 中已经移除了)。

430

这真是再好不过了。

毕竟,任何解决方案都得读取整个文件,弄清楚里面有多少个 \n,然后把结果返回。

你有没有更好的办法,不用读取整个文件?不太确定……最好的解决方案总是受限于输入输出,能做的就是确保不使用多余的内存,但看起来你已经考虑到了这一点。

[编辑于2023年5月]

正如许多其他回答中提到的,在Python 3中有更好的选择。使用 for 循环并不是最高效的。例如,使用 mmap 或缓冲区会更有效。

撰写回答