有效地比较非常大的文件的内容

时间:2016-09-08 15:02:14

标签: python performance file io

我需要快速比较两种不同格式的文件,我不知道该怎么做。如果有人能指出我正确的方向,我将非常感激。

我正在使用CentOS 6,我对Python最熟悉(Python 2和Python 3都可用)。

问题

我希望比较两个大文件的内容(快速)。不幸的是,这些文件的内容不同;在比较它们之前,我需要修改一个的内容。它们组织得不是特别好,所以我不能逐行向下移动并逐行比较。

以下是文件示例:

File 1                    File 2
Job,Time                  Job,Start,End
0123,3-00:00:00           0123,2016-01-01T00:00:00,2016-01-04T00:00:00
1111,05:30:00             1111,2016-01-01T00:00:00,2016-01-01T05:30:00
0000,00:00:05             9090.abc,2016-01-01T12:00:00,2016-01-01T22:00:00
9090.abc,10:00:00         0000,2015-06-01T00:00:00,2015-06-01T00:00:05
...                       ...

我想比较线条的内容与#34; Job"字段,如:

Job        File 1 Content   File 2 Content
0123       3-00:00:00       2016-01-01T00:00:00,2016-01-04T00:00:00
1111       05:30:00         2016-01-01T00:00:00,2016-01-01T05:30:00
0000       00:00:05         2015-06-01T00:00:00,2015-06-01T00:00:05
9090.abc   10:00:00         2016-01-01T12:00:00,2016-01-01T22:00:00
...        ...              ...

我将对文件1内容文件2内容进行计算,并比较两者(每行)。

最有效的方法是什么(匹配线)?

当前系统为另一个文件中的每一行完整地循环一个文件(直到找到匹配项)。此过程可能需要数小时才能完成,文件总是在增长。我希望尽可能高效地比较它们,但即使是性能上的微小改进也会产生极大的影响。

我感谢任何帮助。

谢谢!

5 个答案:

答案 0 :(得分:1)

如果您可以找到利用哈希表的方法,您的任务将从O(N ^ 2)更改为O(N)。实施将取决于文件的确切大小以及文件2中是否有重复的作业ID。我们假设您没有任何重复项。如果你可以将文件2放在内存中,只需将这个东西加载到pandas中,并将job作为索引。如果你不能将文件2放在内存中,你至少可以构建一个{Job#:row#in file 2}的字典。无论哪种方式,找到一个匹配应该快得多。

答案 1 :(得分:1)

我能想到的最有效的方法是使用一些现代Linux系统应该具备的标准UNIX工具。我知道这不是一个python解决方案,但你决定使用python似乎主要建立在你已经知道的语言而不是任何外部约束。鉴于此任务使用UNIX工具有多么简单,我将在此处概述该解决方案。

您要做的是标准的数据库式连接,您可以在两个共享列的表中查找信息。为此,必须对文件进行排序,但是UNIX sort使用了一种有效的算法,您无法将文件排序或复制到数据结构中,这意味着某种排序。

演示版的长版

tail -n+2 file1.csv | LC_ALL=C sort -t , -k 1  > file1.sorted.csv
tail -n+2 file2.csv | LC_ALL=C sort -t , -k 1  > file2.sorted.csv
join -a 1 -a 2 -t , -1 1 -2 1 file1.sorted.csv file2.sorted.csv \
   > joined.csv

tail -n+2切断包含标题的文件的第一行。 -t ,部分将逗号设置为列分隔符,-k 1表示对第一列进行排序。 -1 1 -2 1表示“使用第一个文件的第一列和第二个文件的第一列作为两个文件的共享列”。 -a 1 -a 2表示“还输出文件1和文件2中的行,在其他文件中找不到匹配的行。这与数据库术语中的”完全外部联接“有关。请参阅this SO question和其他人LC_ALL=C

如果您想避免保存临时文件,可以使用bash的“进程替换”<( ... )

进行即时排序
join -a 1 -a 2 -t , -1 1 -2 1 \
    <( tail -n+2 file1.csv | LC_ALL=C sort -t , -k 1 ) \
    <( tail -n+2 file2.csv | LC_ALL=C sort -t , -k 1 ) \
    > joined.csv

请注意,sort支持多个核心(请参阅--parallel中的man sort)如果您的文件太大,以至于在一台计算机上对文件进行排序所需的时间比拆分它的时间长,请发送块通过网络,在多台计算机上对它们进行排序,将它们发回并合并已排序的部分,请参阅this blog-post

答案 2 :(得分:1)

这是将File 2格式转换为File 1格式的简单实用程序(我希望我理解正确的问题,使用python 2) 将代码保存到文件util1.py,例如

import time
import sys

if __name__ == '__main__':
    if  len(sys.argv) < 2:
        print 'Err need filename'
        sys.exit()
    with open(sys.argv[1], 'r') as f:
        line = f.next()
        for line in f:
            jb, start, end =  line.rstrip().split(',')
            dt_format ='%Y-%m-%dT%H:%M:%S'
            start = time.strptime(start, dt_format)
            end = time.strptime(end, dt_format)
            delt = time.mktime(end) - time.mktime(start)
            m, s = divmod(delt, 60)
            h, m = divmod(m, 60)
            d, h = divmod(h, 24)
            if d !=0:
                print '{0},{1:d}-{2:02d}:{3:02d}:{4:02d}'.format(jb, int(d), int(h), int(m), int(s))
            else:
                print '{0},{2:02d}:{3:02d}:{4:02d}'.format(jb, int(d), int(h), int(m), int(s))
然后跑 python ./util1.py f2.txt > f2-1.txt 这将输出保存到f2-1.txt

然后

cp f1.txt f1_.txt

Job,Start,End

删除标题行f1_.txt

sort f1_.txt > f1.sorted.txt

sort f2-1.txt > f2-1.sorted.txt

diff -u f1.sorted.txt f2-1.sorted.txt

答案 3 :(得分:1)

我正在尝试开发一些内容,您可以将其中一个文件拆分为较小的文件(比如每个100,000条记录)并保留每个文件的腌制字典,其中包含所有Job_id作为密钥行作为值。从某种意义上说,每个数据库的索引都可以在每个子文件上使用哈希查找来确定是否要读取其内容。

但是,您说文件不断增长,每个Job_id都是唯一的。所以,我会咬紧牙关并进行一次当前的分析。有一个行计数器,记录您为每个文件分析的行数,并在某处写入文件。然后,您可以使用linecache来了解您要在file1file2进行下一次分析时要开始的行;之前的所有行都已处理完毕,因此再次扫描该文件的整个内容绝对没有意义,只需从上一次分析结束的地方开始。

如果您以足够频繁的间隔运行分析,那么谁会关心它是否为O(n ^ 2),因为您一次处理10个记录并将其附加到组合数据库。换句话说,第一次分析需要很长时间,但每次后续分析都会变得更快,最终n会收敛1,因此它变得无关紧要。

答案 4 :(得分:1)

解析每个文件并将数据转换为datetime.timedelta个对象。创建一个字典,将作业编号作为键,将timedelta对象作为值:

import operator, datetime, collections
def parse1(fp = 'job-file1.txt'):
    with open(fp) as f:
        next(f)
        for line in f:
            line = line.strip()
            job, job_length = line.split(',',1)
            if '-' in job_length:
                days, time = job_length.split('-')
                hours, minutes, seconds = time.split(':')
            else:
                days = 0
                hours, minutes, seconds = job_length.split(':')
            job_length = datetime.timedelta(days = int(days),
                                            hours = int(hours),
                                            minutes = int(minutes),
                                            seconds = int(seconds))
            yield (job, job_length)

fmt = '%Y-%m-%dT%H:%M:%S'
def parse2(fp = 'job-file2.txt'):
    with open(fp) as f:
        next(f)
        for line in f:
            line = line.strip()
            job, start, end = line.split(',')
            job_length = datetime.datetime.strptime(end, fmt) - datetime.datetime.strptime(start, fmt)
            yield (job, job_length)

现在你可以保留两个timedelta对象并在以后比较它们:

# {job_number:[timedelta1, timedelta2]
d = collections.defaultdict(list)

for key, value in parse1():
    d[key].append(value)

for key, value in parse2():
    d[key].append(value)

这可以让您执行以下操作:

differences = {job:lengths for job,lengths in d.items() if not operator.eq(*lengths)}
print(differences)

或者您可以将file1和file2作业长度之间的差异保持为值

d = {key:value for key, value in parse1()}
for key, value in parse2():
    d[key] -= value

然后你只需检查与

的差异
[job for job, difference in d.items() if difference.total_seconds() != 0]