【问题标题】:Cheap way to search a large text file for a string在大型文本文件中搜索字符串的廉价方法
【发布时间】:2010-10-08 19:56:37
【问题描述】:

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

【问题讨论】:

  • 5000 行?这不是“相当大”:-)
  • 行块?听起来您的优化成本比节省的要多(仅针对 5000 行文件......)。你不是在一个循环中连接字符串,是吗? :)
  • 什么是“相当大”? @eumiro
  • @OuuGiii 一个比你的内存还大的文件,所以你不能一次读取它。

标签: python


【解决方案1】:

如果是“相当大”的文件,则按顺序访问这些行,不要将整个文件读入内存:

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

【讨论】:

  • 注意:如果你的字符串跨越多行,这不起作用。
  • @bfontaine 如果是多行怎么办?
  • 在我的情况下它太慢了。
【解决方案2】:

你可以做一个简单的查找:

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

如果你能侥幸成功,一个简单的查找将比正则表达式快很多。

【讨论】:

  • 我现在只是在那里尝试了这段代码,但我正在打印答案以找出它是什么,为什么当找不到字符串时答案等于“-1”但找到时,答案可能有很多不同的数字?
  • @MarkO'Sullivan find 命令返回第一个匹配的索引。 -1 表示不匹配;其他值是起始索引
  • 这段代码效率低下。 f.read() 会将整个文件加载到内存中,这在处理非常大的文件时无用且速度慢。最好在每行基础上进行迭代(使用生成器或简单的 for 循环)
  • @Vinny 如果您要查找的字符串跨越多行,则逐行迭代不起作用。这个答案在内存中可能效率不高,但如果您的文件不是那么大(并且 5000 行不是一个大文件:)),它可能是最好的答案。
  • @bfontaine 关于“最佳”答案,我认为这不是所提问题的最佳答案。如果这应该是针对大文件的(正如标题所暗示的那样,以及 Web 引擎搜索将会到达的原因;我们不仅限于 OP 的 5000 行规范,还应该寻求将自己优化为资源),那么 @987654321 @ 是理想的,既快速又节省内存。毕竟,going into swap 会让你的程序减速到急停。
【解决方案3】:

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

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 文件中查找文件的签名,这非常快且不占用太多内存,您也可以使用更大的缓冲区来加快速度。

【讨论】:

  • “s”应该代表什么?哦,也许这是您要查找的字符串?是的,我现在看到了。
【解决方案4】:

我尝试了一个文件文本搜索的多处理示例。这是我第一次尝试使用多处理模块;我是蟒蛇n00b。非常欢迎评论。我必须等到上班才能测试真正的大文件。它在多核系统上应该比单核搜索更快。呸!找到文本并可靠地报告行号后,如何停止进程?

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' )

【讨论】:

    【解决方案5】:

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

    有了这个,您可以访问该文件,就好像它已经加载到内存中一样,并且操作系统会尽可能地将其映射进出。此外,如果您从 2 个独立进程执行此操作并且它们映射文件“共享”,它们将共享底层内存。

    一旦映射,它将表现得像bytearray。您可以使用正则表达式、find 或任何其他常用方法。

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 取决于有多大。如果它大约 1MB,我希望这种方式比加载整个内容要慢,因为每次读取所有 256 个块的延迟。如果有的话,我希望每次都读取更大的块。也许是一个测试......
      • 延迟确实可能更多,但不一定,重要的是读取物理块大小的倍数,不要浪费读取数据。事实上,我不会将 1mb 的文本文件称为“巨大”,我想的东西大约是几百兆字节。我 100% 同意你的看法,如果文件小于 10 甚至 50mb 不值得分块阅读。
      【解决方案7】:

      我喜欢 Javier 的解决方案。我没试过,但听起来很酷!

      要阅读任意大文本并想知道它是否存在字符串,请将其替换,您可以使用Flashtext,这比使用非常大文件的正则表达式要快。

      编辑:

      来自开发者页面:

      >>> from flashtext import KeywordProcessor
      >>> keyword_processor = KeywordProcessor()
      >>> # keyword_processor.add_keyword(<unclean name>, <standardised name>)
      >>> keyword_processor.add_keyword('Big Apple', 'New York')
      >>> keyword_processor.add_keyword('Bay Area')
      >>> keywords_found = keyword_processor.extract_keywords('I love Big Apple and Bay Area.')
      >>> keywords_found
      >>> # ['New York', 'Bay Area']
      

      或者提取偏移量的时候:

      >>> from flashtext import KeywordProcessor
      >>> keyword_processor = KeywordProcessor()
      >>> keyword_processor.add_keyword('Big Apple', 'New York')
      >>> keyword_processor.add_keyword('Bay Area')
      >>> keywords_found = keyword_processor.extract_keywords('I love big Apple and Bay Area.', span_info=True)
      >>> keywords_found
      >>> # [('New York', 7, 16), ('Bay Area', 21, 29)]
      

      【讨论】:

      • 尽量在你的答案中提供一个简约的例子,这样更多的人可能会从你的答案中得到帮助。
      【解决方案8】:

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

      它还添加了一些检查:

      • 在空文件中搜索空字符串时会正确返回0。在 laurasia 的回答中,这是一个极端情况,将返回 -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)
      

      【讨论】:

        【解决方案9】:

        5000 行并不大(嗯,取决于行有多长...)

        无论如何:假设字符串将是一个单词并且将由空格分隔...

        lines=open(file_path,'r').readlines()
        str_wanted="whatever_youre_looking_for"
        
        
            for i in range(len(lines)):
                l1=lines.split()
                for p in range(len(l1)):
                    if l1[p]==str_wanted:
                        #found
                        # i is the file line, lines[i] is the full line, etc.
        

        【讨论】:

        • l1=lines.split() AttributeError: 'list' object has no attribute 'split'
        猜你喜欢
        • 1970-01-01
        • 2011-09-07
        • 2013-04-27
        • 2016-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-12-21
        相关资源
        最近更新 更多