以较便宜的方式搜索大型文本文件中的字符串

时间:2010-10-08 19:56:38

标签: python

我需要在一个非常大的文本文件中搜索特定的字符串。它是一个包含大约5000行文本的构建日志。什么是最好的方式去做?使用正则表达式应该不会造成任何问题吗?我将继续阅读行块,并使用简单的查找。

8 个答案:

答案 0 :(得分:47)

如果它是“非常大”的文件,那么顺序访问这些行并且不要将整个文件读入内存:

with open('largeFile', 'r') as inF:
    for line in inF:
        if 'myString' in line:
            # do_something

答案 1 :(得分:16)

你可以做一个简单的发现:

f = open('file.txt', 'r')
lines = f.read()
answer = lines.find('string')

如果你可以逃脱它,一个简单的发现将比正则表达式快得多。

答案 2 :(得分:12)

以下函数适用于文本文件和二进制文件(尽管只返回字节数中的位置),它确实有利于查找字符串,即使它们与行或缓冲区重叠也不会在搜索行或缓冲区时可以找到。

def fnd(fname, s, start=0):
    with open(fname, 'rb') as f:
        fsize = os.path.getsize(fname)
        bsize = 4096
        buffer = None
        if start > 0:
            f.seek(start)
        overlap = len(s) - 1
        while True:
            if (f.tell() >= overlap and f.tell() < fsize):
                f.seek(f.tell() - overlap)
            buffer = f.read(bsize)
            if buffer:
                pos = buffer.find(s)
                if pos >= 0:
                    return f.tell() - (len(buffer) - pos)
            else:
                return -1

这背后的想法是:

  • 寻找档案中的起始位置
  • 从文件读取缓冲区(搜索字符串必须小于缓冲区大小)但如果不是在开头,则删回-1个字节,以便在最后一个读取缓冲区结束时捕获字符串并且继续下一个。
  • 返回位置,如果未找到则返回-1

我使用这样的东西来查找较大的ISO9660文件中的文件签名,这非常快,并且没有使用太多内存,你也可以使用更大的缓冲区来加快速度。

答案 3 :(得分:5)

我已经开始整理文件文本搜索的多处理示例。这是我第一次使用多处理模块;而我是一个python n00b。评论相当欢迎。我必须等到工作才能测试真正的大文件。它在多核系统上应该比单核搜索更快。 Bleagh!如何找到文本后如何停止流程并可靠地报告行号?

import multiprocessing, os, time
NUMBER_OF_PROCESSES = multiprocessing.cpu_count()

def FindText( host, file_name, text):
    file_size = os.stat(file_name ).st_size 
    m1 = open(file_name, "r")

    #work out file size to divide up to farm out line counting

    chunk = (file_size / NUMBER_OF_PROCESSES ) + 1
    lines = 0
    line_found_at = -1

    seekStart = chunk * (host)
    seekEnd = chunk * (host+1)
    if seekEnd > file_size:
        seekEnd = file_size

    if host > 0:
        m1.seek( seekStart )
        m1.readline()

    line = m1.readline()

    while len(line) > 0:
        lines += 1
        if text in line:
            #found the line
            line_found_at = lines
            break
        if m1.tell() > seekEnd or len(line) == 0:
            break
        line = m1.readline()
    m1.close()
    return host,lines,line_found_at

# Function run by worker processes
def worker(input, output):
    for host,file_name,text in iter(input.get, 'STOP'):
        output.put(FindText( host,file_name,text ))

def main(file_name,text):
    t_start = time.time()
    # Create queues
    task_queue = multiprocessing.Queue()
    done_queue = multiprocessing.Queue()
    #submit file to open and text to find
    print 'Starting', NUMBER_OF_PROCESSES, 'searching workers'
    for h in range( NUMBER_OF_PROCESSES ):
        t = (h,file_name,text)
        task_queue.put(t)

    #Start worker processes
    for _i in range(NUMBER_OF_PROCESSES):
        multiprocessing.Process(target=worker, args=(task_queue, done_queue)).start()

    # Get and print results

    results = {}
    for _i in range(NUMBER_OF_PROCESSES):
        host,lines,line_found = done_queue.get()
        results[host] = (lines,line_found)

    # Tell child processes to stop
    for _i in range(NUMBER_OF_PROCESSES):
        task_queue.put('STOP')
#        print "Stopping Process #%s" % i

    total_lines = 0
    for h in range(NUMBER_OF_PROCESSES):
        if results[h][1] > -1:
            print text, 'Found at', total_lines + results[h][1], 'in', time.time() - t_start, 'seconds'
            break
        total_lines += results[h][0]

if __name__ == "__main__":
    main( file_name = 'testFile.txt', text = 'IPI1520' )

答案 4 :(得分:2)

如果没有办法告诉字符串将在哪里(上半部分,后半部分等),那么除了内置的“查找”功能之外,确实没有优化的方法来进行搜索。您可以通过不一次性读取文件来减少I / O时间和内存消耗,但是在4kb块(通常是硬盘块的大小)。这不会使搜索更快,除非字符串位于文件的第一部分,但在所有情况下都会减少内存消耗,如果文件很大,这可能是一个好主意。

答案 5 :(得分:0)

我很惊讶没有人提到将文件映射到内存:mmap

通过这种方式,您可以像访问已经加载到内存中一样访问该文件,操作系统将尽可能地将其映射到其中。此外,如果您从2个独立进程执行此操作并将文件映射为“共享”,则它们将共享底层内存。

映射后,它的行为就像bytearray。您可以使用正则表达式,查找或任何其他常用方法。

请注意,此方法特定于操作系统。它不会自动移植。

答案 6 :(得分:0)

这完全受laurasia's answer above的启发,但它改进了结构。

它还添加了一些检查:

  • 在空文件中搜索空字符串时,它将正确返回0。用劳拉西亚的答案,这是一个边缘情况,将返回-1
  • 它还会预先检查目标字符串是否大于缓冲区大小,并在这种情况下引发错误。

实际上,目标字符串应比缓冲区小得多,以提高效率,并且有更有效的方法来搜索目标字符串的大小是否非常接近缓冲区的大小。

def fnd(fname, goal, start=0, bsize=4096):
    if bsize < len(goal):
        raise ValueError("The buffer size must be larger than the string being searched for.")
    with open(fname, 'rb') as f:
        if start > 0:
            f.seek(start)
        overlap = len(goal) - 1
        while True:
            buffer = f.read(bsize)
            pos = buffer.find(goal)
            if pos >= 0:
                return f.tell() - len(buffer) + pos
            if not buffer:
                return -1
            f.seek(f.tell() - overlap)

答案 7 :(得分:-1)