【问题标题】:using python to search extremely large text file使用python搜索超大文本文件
【发布时间】:2012-10-03 07:03:17
【问题描述】:

我有一个 4000 万行、3 GB 的大文本文件(可能无法放入内存),格式如下:

399.4540176 {Some other data}
404.498759292 {Some other data}
408.362737492 {Some other data}
412.832976111 {Some other data}
415.70665675 {Some other data}
419.586515381 {Some other data}
427.316825959 {Some other data}
.......

每一行都以一个数字开头,然后是一些其他数据。数字按排序顺序排列。我需要能够:

  1. 给定一个数字x 和一个范围y,找出编号在y 范围x 内的所有行。例如如果x=20y=5,我需要找到编号在1525 之间的所有行。
  2. 将这些行存储到另一个单独的文件中。

什么是无需遍历整个文件的有效方法?

【问题讨论】:

  • 由于您知道文件的长度并且文件已排序,因此可能是二进制排序的一种变体,以查找范围内具有最小值的行,逐行读取直到到达最大值?
  • 对数据进行排序确实很有帮助。您可以尝试的是:在内存中打开一个文件块,比如 512mb 块。然后看看最后的行号是什么。如果它大于您的范围,请在该块中搜索精确的行号,如果不是,则加载下一个块并重复检查。这样你就不会将整个东西加载到内存中,但是当你找到一个块时,工作在内存中完成得非常快。如果您的范围跨越多个块,您还需要解决问题,但这是可行的。祝你好运,玩得开心!
  • 大概,线条的长度不一样?
  • 是的,线条长度不同。
  • 您需要对文件进行索引,请参阅下面我的回答以了解两种可能性。

标签: python search large-data


【解决方案1】:

如果你不想提前为行长生成数据库,你可以试试这个:

import os
import sys

# Configuration, change these to suit your needs
maxRowOffset = 100  #increase this if some lines are being missed
fileName = 'longFile.txt'
x = 2000
y = 25

#seek to first character c before the current position
def seekTo(f,c):
    while f.read(1) != c:
        f.seek(-2,1)

def parseRow(row):
    return (int(row.split(None,1)[0]),row)

minRow = x - y
maxRow = x + y
step = os.path.getsize(fileName)/2.
with open(fileName,'r') as f:
    while True:
        f.seek(int(step),1)
        seekTo(f,'\n')
        row = parseRow(f.readline())
        if row[0] < minRow:
            if minRow - row[0] < maxRowOffset:
                with open('outputFile.txt','w') as fo:
                    for row in f:
                        row = parseRow(row)
                        if row[0] > maxRow:
                            sys.exit()
                        if row[0] >= minRow:
                            fo.write(row[1])
            else:
                step /= 2.
                step = step * -1 if step < 0 else step
        else:
            step /= 2.
            step = step * -1 if step > 0 else step

它首先对文件执行二进制搜索,直到它靠近(小于maxRowOffset)要查找的行。然后它开始读取每一行,直到找到大于x-y 的行。该行及其之后的每一行都被写入输出文件,直到找到大于x+y 的行,以及程序退出的点。

我在一个 1,000,000 行的文件上进行了测试,它在 0.05 秒内运行。将此与读取每行花费 3.8 秒进行比较。

【讨论】:

    【解决方案2】:

    您需要随机访问文本文件无法获得的行,除非这些行都被填充到相同的长度。

    一种解决方案是将表转储到具有两列的数据库(例如 SQLite)中,一列用于数字,另一列用于所有其他数据(假设保证数据符合允许的最大字符数在您的数据库中的单个列中是)。然后索引数字列就可以了。

    如果没有数据库,您可以一次读取文件并创建一个内存数据结构,其中包含显示包含(数字,行偏移)的成对值。您可以通过添加每行的长度(包括行尾)来计算行偏移。现在您可以在数字上对这些值对进行二进制搜索,并使用偏移量随机访问文件中的行。如果您需要稍后重复搜索,请腌制内存结构并重新加载以供以后重复使用。

    这会读取整个文件(您说过您不想这样做),但只执行一次以构建索引。之后,您可以根据需要对文件执行任意数量的请求,它们会非常快。

    请注意,第二种解决方案实质上是在您的文本文件上创建数据库索引。

    在第二种解决方案中创建索引的粗略代码:

     import Pickle
    
     line_end_length = len('\n') # must be a better way to do this!
     offset = 0
     index = [] # probably a better structure to use than a list
    
     f = open(filename)
     for row in f:
         nbr = float(row.split(' ')[0])
         index.append([nbr, offset])
         offset += len(row) + line_end_length
    
     Pickle.dump(index, open('filename.idx', 'wb')) # saves it for future use
    

    现在,您可以对列表执行二进制搜索。可能有一个比列表更好的数据结构来累积索引值,但我必须阅读各种集合类型。

    【讨论】:

    • 我对您描述的第二种方法很感兴趣。你有这方面的示例代码吗?
    • 我怀疑这种方法比我下面提出的解决方案更快并且使用更少的内存。
    • 你可能是对的——我不知道 unix 命令行工具。此外,这取决于这是一次性请求还是将针对静态文件不断重复。如果是后者,这可能会更慢(构建索引),但我认为后续请求会非常快(不到一秒),特别是如果索引可以在内存中维护。
    【解决方案3】:

    由于要匹配第一个字段,可以使用gawk

    $ gawk '{if ($1 >= 15 && $1 <= 25) { print }; if ($1 > 25) { exit }}' your_file
    

    编辑: 获取一个包含 261,775,557 行、大小为 2.5 GiB 的文件,在我的 Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz 上搜索行 50,010,01550,010,025 这需要 27 秒。听起来对我来说已经足够了。

    【讨论】:

    • 不是行号,我认为是每行前面的值。
    • 我假设 qawk 实际上会为每个请求遍历整个文件?
    • 我添加了一些关于这种方法如何执行的示例。
    • 实际上我需要在文件中搜索数百个数字。我可能需要一个更有效的方法,比如二分搜索。
    • 文件大小是限制数,不是你要提取的行数。
    【解决方案4】:

    为了找到以刚好高于下限的数字开头的行,您必须逐行遍历文件,直到找到该行。没有其他办法,即必须读取文件中的所有数据并解析换行符。

    我们必须将此搜索运行到超过您的上限的第一行并停止。因此,它有助于文件已经排序。这段代码希望能有所帮助:

    with open(outpath) as outfile:
        with open(inpath) as infile:
            for line in infile:
                t = float(line.split()[0])
                if lower_limit <= t <= upper_limit:
                    outfile.write(line)
                elif t > upper_limit:
                    break
    

    我认为理论上没有其他选择。

    【讨论】:

    • 可能不正确:当t 的第一个值高于upper_limit 也高于upper_limit 时。将lower_limit &lt;= t 替换为lower_limit &lt;= t &lt; upper_limit
    • 这正是我不想做的。而且文件太大,无法作为一个整体加载到内存中。
    • @Loccsta:使用此方法,文件不会加载到内存中。这是一种基于行的方法(从输入文件中读取一行,然后删除或写入输出文件。它不保存在内存中)。此外,我很想看看您如何在不搜索文件中的换行符的情况下找到满足标准的第一行:)。请记住,您的行的长度不相等。
    • you have to go through the file line by line 不正确,你可以使用 file.seek() 来跳转文件,可能会节省很多时间
    • @Matt:我同意您可以随机查看某处,从那里搜索下一个或上一个换行符,读取数字并通过嵌套间隔的方法重复该操作。但是,我认为这项任务不值得发明专门的算法。重要的是我们不要将文件读入内存,仅此而已。
    猜你喜欢
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 2012-03-10
    • 1970-01-01
    • 2022-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多