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

时间:2015-12-23 11:54:25

标签: python parsing text bigdata

我正在尝试使用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功能,但运行时间的改善相当轻微。
因此,我采用了另一种方法 - 随机搜索文件。我所做的是以字节为单位获取文件的大小,计算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的总大小不同,那么我就遇到了问题。
那么,您是否有任何解决方法的想法,而不必诉诸天真的迭代解决方案?在执行搜索之后,也许有办法说出我在哪一行? (无法找到一个......)此时我真的没有想法,所以任何帮助/提示都会受到赞赏。

谢谢!

3 个答案:

答案 0 :(得分:1)

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

即使两个文件的长度相同,内部仍然会有不同长度的行。

现在,如果您在同一文件的不同部分上多次执行这些统计信息,则可以执行以下操作:

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

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

同时扫描两个文件时,请确保使用一些缓冲来避免大量的硬盘搜索。

修改

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

使用mmap可以大大加快速度(每次只需知道线条的位置就不需要读取读取线):只需在文件的部分上映射视图,然后扫描映射的内存即可。换行符(十六进制的\ n或0x0a)。这应该不会超过读取文件所需的时间。

答案 1 :(得分:1)

Unix文件只是字符流,因此无法寻找给定的行,或找到与给定字符或该表单中的任何其他内容对应的行号。

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

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

将打印/path/to/file的前60,000,000行中的字符数。

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

如果您的文件没有更改,您可以创建将行号映射到字符位置的索引。构建索引后,寻找所需的行号将非常快。如果读取20%的文件需要半个小时,那么构建两个索引大约需要5个小时,但如果你只需要做一次,你可以让它在一夜之间运行。

答案 2 :(得分:0)

好的,所以感谢有趣的答案,但这就是我实际上最终要做的事情:

首先,我估计文件中的行数,而不是实际计算它们。由于我的文件是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 - 如果没有这个,你最终会在整个文件上运行。

这个解决方案没有使用随机搜索那么快,但它对我来说已经足够好了。它还包括一些不准确之处,但在这种特殊情况下它并不会让我感到烦恼 我很高兴听到你对这个解决方案的看法。