在Python中高效遍历大文件(10GB+)的方法

12 投票
5 回答
11140 浏览
提问于 2025-04-16 18:50

我正在写一个Python脚本,目的是处理两个文件——一个文件里有一堆UUID,另一个文件里有很多日志条目,每一行都包含一个来自第一个文件的UUID。这个程序的目标是先从第一个文件中创建一个UUID的列表,然后每当在日志文件中找到这个UUID时,就把它的计数加一。

简单来说,就是统计每个UUID在日志文件中出现的次数。目前,我有一个列表,里面用UUID作为键,'hits'(命中次数)作为值。然后我又写了一个循环,逐行读取日志文件,检查日志中的UUID是否和UUID列表中的某个UUID匹配。如果匹配,就把对应的值加一。

    for i, logLine in enumerate(logHandle):         #start matching UUID entries in log file to UUID from rulebase
        if logFunc.progress(lineCount, logSize):    #check progress
            print logFunc.progress(lineCount, logSize)  #print progress in 10% intervals
        for uid in uidHits:
            if logLine.count(uid) == 1:             #for each UUID, check the current line of the log for a match in the UUID list
                uidHits[uid] += 1                   #if matched, increment the relevant value in the uidHits list
                break                                #as we've already found the match, don't process the rest
        lineCount += 1               

这个方法是有效的——但我觉得还有更高效的方式来处理这个文件。我查阅了一些资料,发现使用'count'比用编译的正则表达式要快。我原以为一次读取文件的一部分,而不是逐行读取,会提高性能,因为这样可以减少磁盘读写的时间,但在一个大约200MB的测试文件上,性能差异几乎可以忽略不计。如果有人有其他的方法,我会非常感激 :)

5 个回答

3

就像上面的人说的,处理一个10GB的文件时,你的硬盘空间可能很快就会不够用。如果只是想优化代码,使用生成器的建议非常好。在Python 2.x中,它的写法大概是这样的:

uuid_generator = (line.split(SPLIT_CHAR)[UUID_FIELD] for line in file)

听起来这个问题其实不一定非得用Python来解决。如果你只是简单地在数UUID,使用Unix命令可能会比用Python更快解决你的问题。

cut -d${SPLIT_CHAR} -f${UUID_FIELD} log_file.txt | sort | uniq -c 
3

这不是一个简单的五行回答,但在2008年的PyCon大会上,有一个很棒的教程,叫做系统程序员的生成器技巧。还有一个后续的教程,叫做关于协程和并发的有趣课程

这个生成器教程特别用处理大日志文件作为例子。

14

从功能的角度思考!

  1. 写一个函数,这个函数可以接收日志文件中的一行,并返回一个唯一标识符(uuid)。我们可以叫这个函数 uuid

  2. 把这个函数应用到日志文件的每一行。如果你使用的是Python 3,可以用内置的map函数;如果不是,就需要用itertools.imap。

  3. 把这个迭代器传给collections.Counter。

    collections.Counter(map(uuid, open("log.txt")))
    

这样做会非常高效。

几点说明:

  • 这个方法完全忽略了UUID的列表,只是统计出现在日志文件中的UUID。如果你不想这样,你需要对程序做一些修改。

    • 你的代码运行得慢是因为你使用了错误的数据结构。这里你应该用字典(dict)。

撰写回答