Python)尽可能快地计算巨大(>10GB)文件中的行数

34

我现在有一个非常简单的脚本,使用enumerate()来计算文本文件中的行数:

i = 0
f = open("C:/Users/guest/Desktop/file.log", "r")
for i, line in enumerate(f):
      pass
print i + 1
f.close()

这需要大约3分半钟来处理一个15GB的日志文件,其中包含约3千万行记录。如果能在两分钟或更短时间内完成处理,那就太好了,因为这些都是每天的日志,而我们需要对一个月的日志进行分析,因此代码将需要处理约30个大小为15GB的日志文件——可能需要1个半小时以上的时间,我们希望尽量减少服务器的时间和内存负荷。

我也可以接受一个好的近似/估算方法,但它需要精确到4个有效数字...

谢谢!


3
通常来说,将文件视为二进制数据处理可能会更快,按合理大小的块(比如每次4KB)读取它,并在读取时计算每个块中\n字符的数量。 - aroth
4
这种写法并不比你的朴素解决方案更优,但是请注意,用Pythonic的方式来编写你这里的代码,只需要简单地写成 with open(fname) as f: print sum(1 for line in f) - wim
1
aroth:谢谢你的提示,我应该研究一下。 wim:太好了,谢谢,这样会简短得多... - Adrienne
请查看迈克尔·贝肯的回答中的rawbigcount,它可能对您有所帮助! - Diogo
5个回答

49

Ignacio的回答是正确的,但如果您使用32位进程,则可能会失败。

但也许逐块读取文件然后计算每个块中的\n字符数会更有用。

def blocks(files, size=65536):
    while True:
        b = files.read(size)
        if not b: break
        yield b

with open("file", "r") as f:
    print sum(bl.count("\n") for bl in blocks(f))

会完成你的工作。

请注意,我没有以二进制方式打开文件,因此\r\n将被转换为\n,使得计数更加可靠。

对于Python 3,为了读取包含各种字符的文件而更加健壮:

def blocks(files, size=65536):
    while True:
        b = files.read(size)
        if not b: break
        yield b

with open("file", "r",encoding="utf-8",errors='ignore') as f:
    print (sum(bl.count("\n") for bl in blocks(f)))

1
仅作为一个数据点,使用朴素方法读取大约51MB的文件需要约1分钟,而使用这种方法只需要不到1秒钟。 - M Katz
6
现在是“一个大文件”还是“大约51 MB的文件”?;-) - glglgl
这个解决方案可能会漏掉最后一行,但对于一个巨大的文件来说可能并不重要。 - minhle_r7
@ngọcminh.oss 只有当最后一行不完整时才会定义文本文件以换行符结尾,请参见 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206 和 https://dev59.com/b3RB5IYBdhLWcg3wET1J#729795。 - glglgl
2
不要太在意定义。当你处理真实数据时,一切都是混乱的。但无论如何,这并不重要。 - minhle_r7
关于缺失行(即没有“换行符”的行):如果文件很大,这可能相对不重要。但我有一些文件大小从巨大到只有一行。不幸的是,其中一些只有一行的文件缺少尾随的换行符,而使用此函数的程序假定返回值为0表示空文件...这可能不是真的。因此,我必须进行其他检查。 - Mike Maxwell

24

我知道这有点不公平,但你可以这样做

int(subprocess.check_output("wc -l C:\\alarm.bat").split()[0])

如果你在使用Windows系统,可以查看Coreutils


我的解决方案只需要1分37秒的实际时间。 - Jakob Bowyer
1
这是快得多。 - Hanan Shteingart
4
似乎你需要在 Python3 中执行 int(subprocess.check_output("/usr/bin/wc -l cred", shell=True).split()[0]) 的命令。 - ZN13
1
如果您有大文件或大量文件,如果您想要纯粹的性能而不想使用其他语言,请考虑使用这种方法。 - Victor 'Chris' Cabral

17

一种快速的、只需一行代码的解决方案是:

sum(1 for i in open(file_path, 'rb'))

它应该适用于任意大小的文件。


我确认这是最快的方法(除了wc -l的hack)。使用文本模式会稍微降低一点性能,但与其他解决方案相比微不足道。 - ei-grad
1
顺便提一下,有一个不必要的额外生成器括号。 - ei-grad
1
去掉不必要的生成器括号后,它看起来稍微快一些(每个timeit),并且在100,000行文件的每个memit中消耗大约3MB的内存。 - mikey
如果文件是带有换行符的文本文件,则似乎无法正常工作。我的问题是需要对大型txt文件进行字符计数。 - Math is Hard
3
文件未关闭。 - Jeyekomon

5

mmap文件,并计算新行数。

import mmap

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

10
请考虑添加一个简短的示例来展示这个问题,谢谢! - Kumba
5
短小的例子可能是一个好主意,我同意。 - Kailegh

2
我会扩展gl的答案,并使用Python的multiprocessing模块运行他/她的代码,以加快计数速度:
def blocks(f, cut, size=64*1024): # 65536
    start, chunk =cut
    iter=0
    read_size=int(size)
    _break =False
    while not _break:
        if _break: break
        if f.tell()+size>start+chunk:
            read_size=int(start+chunk- f.tell() )
            _break=True
        b = f.read(read_size)
        iter +=1
        if not b: break
        yield b


def get_chunk_line_count(data):
    fn,  chunk_id, cut = data
    start, chunk =cut
    cnt =0
    last_bl=None

    with open(fn, "r") as f:
        if 0:
            f.seek(start)
            bl = f.read(chunk)
            cnt= bl.count('\n')
        else:
            f.seek(start)
            for i, bl  in enumerate(blocks(f,cut)):
                cnt +=  bl.count('\n')
                last_bl=bl

        if not last_bl.endswith('\n'):
            cnt -=1

        return cnt
....
pool = multiprocessing.Pool(processes=pool_size,
                            initializer=start_process,
                            )
pool_outputs = pool.map(get_chunk_line_count, inputs)
pool.close() # no more tasks
pool.join() 

这将使计数性能提高20倍。我将它封装到一个脚本中,并将其放在Github上。


@olekb感谢您分享多进程处理的方法。作为一个新手,我有个问题,我们如何运行这段代码来计算一个大文件(比如'myfile.txt')中的行数?我尝试使用pool = multiprocessing.Pool(4); pool_outputs = pool.map(get_chunk_line_count, 'myfile.txt'),但是会引发错误。非常感谢您的回答! - user1330974

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接