两个大文本文件的高效文件比较
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++ 中重新完成,例如,以进一步加快速度并进一步减少内存需求。
【讨论】:
以上是关于两个大文本文件的高效文件比较的主要内容,如果未能解决你的问题,请参考以下文章