如何在两个巨大的文本文件中跳转到同一行?

How to jump to the same line in two huge text files?

我正在尝试使用 python 对巨大的文本文件进行一些操作,这里所说的巨大是指超过 100GB。具体来说,我想从文件的行中提取样本。例如,假设我有一个大约有 3 亿行的文件,我只想拿一百万行,将它们写入一个新文件,稍后分析它们以获得一些统计数据。问题是,我无法从第一行开始,因为文件的第一部分不足以代表文件的其余部分。因此,我必须将大约 20% 的内容放入文件中,然后开始提取行。如果我以天真的方式进行操作,则需要很长时间(在我的机器上需要 20-30 分钟)才能达到 20% 的线。例如,再次假设我的文件有 3 亿行,我想从第 60,000,000 (20%) 行开始采样。我可以做类似的事情:

start_in_line = 60000000
sample_size = 1000000
with open(huge_file,'r') as f, open(out_file,'w') as fo:
    for x in range(start_in_line):
        f.readline()
    for y in range(sample_size):
        print(f.readline(),file=fo)

但是正如我所说,这非常慢。我尝试使用一些不那么幼稚的方法,例如 itertools 函数,但是 运行ning 时间的改善相当小。
因此,我采用了另一种方法——随机搜索文件。我所做的是以字节为单位获取文件的大小,计算它的 20%,然后搜索这个字节。例如:

import os
huge_file_size = os.stat(huge_file).st_size
offset_percent = 20
sample_size = 1000000

start_point_byte = int(huge_file_size*offset_percent/100)
with open(huge_file) as f, open(out_file,'w') as fo:
    f.seek(start_point_byte)
    f.readline()    # get to the start of next line
    for y in range(sample_size):
        print(f.readline(),file=fo)

这种方法非常有效,但是!
我总是使用成对的文件。我们称它们为 R1 和 R2。 R1 和 R2 将始终具有相同的行数,并且我 运行 我在每一行上都使用了我的采样脚本。对于我的下游分析来说,从 R1 和 R2 中获取的样本与采样线进行协调是至关重要的。例如,如果我最终从 R1 的第 60,111,123 行开始采样,我必须从 R2 中的同一行开始采样。即使我错过了一行,我的分析也注定失败。如果 R1 和 R2 的大小完全相同(有时是这种情况),那么我没有问题,因为我的 f.seek() 将使我到达两个文件中的相同位置。但是,如果文件之间的行长度不同,即 R1 和 R2 的总大小不同,那么我就有问题了。
那么,您是否有任何解决方法的想法,而不必求助于朴素的迭代解决方案?在执行搜索后,也许有一种方法可以告诉我我在哪一行? (找不到……)我现在真的没有想法,所以任何 help/hint 将不胜感激。

谢谢!

如果每个文件中的行可以有不同的长度,除了首先扫描它们之外别无他法(除非每行上有某种形式的唯一标识符,这在两个文件中都是相同的)。

即使两个文件的长度相同,里面仍然可能有不同长度的行。

现在,如果您对同一文件的不同部分进行多次统计,您可以执行以下操作:

  • 对两个文件进行一次扫描,并将每一行的文件位置存储在第三个文件中(最好是二进制形式(2 x 64 位值)或至少相同的宽度,以便您可以直接跳到你想要的线的位置对,然后你可以计算)。

  • 然后只需使用这些文件位置来访问两个文件中的行(您甚至可以从第三个文件中的不同文件位置计算出您需要的块的大小)。

同时扫描两个文件时,确保使用一些缓冲以避免大量的硬盘寻道。

编辑:

我不知道Python(我是一名C++程序员),但我快速搜索了一下,似乎Python也支持内存映射文件(mmap)。

使用 mmap 你可以显着加快速度(不需要每次都做一个 readline 只是为了知道行的位置):只需在你的文件的一部分上映射一个视图并扫描映射的内存对于换行符(\n 或十六进制的 0x0a)。这应该不会超过读取文件所需的时间。

Unix 文件只是字符流,因此无法查找给定的行,或找到与给定字符对应的行号,或任何其他形式。

您可以使用标准实用程序来查找一行的字符位置。例如,

head -n 60000000 /path/to/file | wc -c

将打印 /path/to/file.

前 60,000,000 行的字符数

虽然这可能比使用 python 快,但不会很快;它受磁盘读取速度的限制。如果您需要读取 20GB,则需要几分钟。但是至少尝试一次来校准您的 python 程序是值得的。

如果您的文件没有改变,您可以创建将行号映射到字符位置的索引。建立索引后,查找所需行号的速度将非常快。如果读取20%的文件需要半个小时,那么构建两个索引大约需要5个小时,但如果只需要一次,可以放着运行过夜。

好的,感谢您提供有趣的答案,但这就是我最终做的事情:

首先,我估计了文件中的行数,但没有实际计算它们。因为我的文件是 ASCII,我知道每个字符占用 1 个字节,所以我得到字符数,比如说,前 100 行,然后得到文件的大小并使用这些数字得到(相当粗略的)估计的行数。在这里要说一下,虽然我的线可能长短不一,但都在一个有限的范围内,所以这个估计是合理的。
一旦我有了它,我就使用 Linux sed 命令的系统调用来提取一系列行。所以假设我的文件确实有 3 亿行,我估计它有 2.5 亿行(我得到了更好的估计,但对我来说这并不重要)。我使用 20% 的偏移量,所以我想从第 50,000,000 行开始采样并取 1,000,000 行。我愿意:

os.system("sed -n '50000000,51000000p;51000000q' in_file > out_file")

请注意 51000000q - 没有它,您将在整个文件中得到 运行。

此解决方案不如使用随机搜索快,但对我来说已经足够了。它还包括一些不准确之处,但在这种特定情况下我并不介意。
我很高兴听到您对此解决方案的意见。