两个大文本文件的高效文件比较

Posted

技术标签:

【中文标题】两个大文本文件的高效文件比较【英文标题】:Efficient file comparison of two large text files 【发布时间】:2017-10-12 03:26:14 【问题描述】:

在我们的用例中,我们从客户(大小约为 30GB)处获得包含数百万条记录的大型快照文本文件(tsv、csv 等)。数据如下所示:

ItemId (unique), Title, Description, Price etc.
shoe-id1, "title1", "desc1", 10
book-id-2, "title2", "desc2", 5

每当我们从客户那里获得快照时,我们都需要计算“增量”:

    Inserted - 插入的记录(仅存在于最新文件中,而不存在于前一个文件中),

    已更新 - 任何其他列中的 Id 相同但值不同

    已删除(仅存在于以前的文件中,而不是最新的文件中)。

(数据可能在后续文件中出现乱序,并没有真正按任何列排序)。

我们需要每天为不同的客户运行多次。

我们目前将快照文件 1 中的所有数据存储到 SQL 服务器(12 个分片(按 customerId 分区),总共包含十亿行),并在收到快照文件 2 时使用多个查询计算差异。事实证明这是非常低效的(小时,删除特别棘手)。我想知道是否有更快的解决方案。我对任何技术(例如 hadoop、nosql 数据库)持开放态度。关键是速度(最好是分钟)。

【问题讨论】:

我正在考虑将唯一 id 读入两个 Perl 散列 - 一个用于旧的用于新的,并且可能是每个记录的剩余字段的 CRC/SHA 校验和作为存储在哈希。检查共同/唯一成员资格应该非常快。尝试添加 Perl 标签。 您提到了文件大小。我可以知道速度是多少吗?意思是,您多久获得一次此后续文件。 一天大约 20K 次 【参考方案1】:

通常,判断 id 是否出现在数据集中的最快方法是通过散列,所以我会创建一个散列,使用 id 作为键,其余列的 MD5 校验和或 CRC 作为存储在该键处的元素。如果您的数据有很多列,那应该可以减轻内存压力。为什么我会这样想?因为你说你有数百万条记录的 GB 数据,所以我推断每条记录必须是千字节的数量级 - 即相当宽。

所以,我可以在 Perl 中合成一个 13M 旧值的哈希值和一个 15M 新值的哈希值,然后找到添加、更改和删除的内容,如下所示。

#!/usr/bin/perl
use strict;
use warnings;

# Set $verbose=1 for copious output
my $verbose=0;

my $million=1000000;
my $nOld=13*$million;
my $nNew=15*$million;

my %oldHash;
my %newHash;
my $key;
my $cksum;
my $i;
my $found;

print "Populating oldHash with $nOld entries\n";
for($i=1;$i<=$nOld;$i++)
   $key=$i-1;
   $cksum=int(rand(2));
   $oldHash$key=$cksum;


print "Populating newHash with $nNew entries\n";
$key=$million;
for($i=1;$i<=$nNew;$i++)
   $cksum=1;
   $newHash$key=$cksum;
   $key++;


print "Part 1: Finding new ids (present in newHash, not present in oldHash) ...\n";
$found=0;
for $key (keys %newHash) 
   if(!defined($oldHash$key))
      print "New id: $key, cksum=$newHashrkey\n" if $verbose;
      $found++;
   

print "Total new: $found\n";

print "Part 2: Finding changed ids (present in both but cksum different) ...\n";
$found=0;
for $key (keys %oldHash) 
   if(defined($newHash$key) && ($oldHash$key!=$newHash$key))
      print "Changed id: $key, old cksum=$oldHash$key, new cksum=$newHash$key\n" if $verbose;
      $found++;
   

print "Total changed: $found\n";

print "Part 3: Finding deleted ids (present in oldHash, but not present in newHash) ...\n";
$found=0;
for $key (keys %oldHash) 
   if(!defined($newHash$key))
      print "Deleted id: $key, cksum=$oldHash$key\n" if $verbose;
      $found++;
   

print "Total deleted: $found\n";

在我的 iMac 上运行需要 53 秒。

./hashes 
Populating oldHash with 13000000 entries
Populating newHash with 15000000 entries
Part 1: Finding new ids (present in newHash, not present in oldHash) ...
Total new: 3000000
Part 2: Finding changed ids (present in both but cksum different) ...
Total changed: 6000913
Part 3: Finding deleted ids (present in oldHash, but not present in newHash) ...
Total deleted: 1000000

出于测试的目的,我使 oldHash 中的键从 0..12,999,999 运行,newHash 中的键从 1,000,000..16,000,000 运行,然后我可以很容易地判断它是否有效,因为新键应该是13,000,000..16,000,000,删除的密钥应该是 0..999,999。我还让checksums 在 0 和 1 之间交替,以便 50% 的重叠 id 看起来不同。


以相对简单的方式完成后,我现在可以看到您只需要校验和部分即可找到更改后的 id,因此您可以在没有校验和的情况下执行第 1 部分和第 3 部分以节省内存。您也可以在加载数据时一次执行第 2 部分一个元素,这样您就不需要预先将所有旧的和所有的新 id 加载到内存中。相反,您将加载旧数据集和新数据集中较小的一个,然后在将另一组 id 读入内存时一次检查一个 id 的更改,这将降低对内存的要求。


最后,如果该方法有效,可以很容易地在 C++ 中重新完成,例如,以进一步加快速度并进一步减少内存需求。

【讨论】:

以上是关于两个大文本文件的高效文件比较的主要内容,如果未能解决你的问题,请参考以下文章

大文本文件的高效迭代

Pyspark 合并两个大文本文件

python 在大文件里面删除某一行,比较有效率的方法

如何提高java读取大文本文件的效率

比较两个文本文件的最快方法是啥,而不是将移动的行计算为不同的

Python - 检查两个大文本文件之间的一致性