如何比较大文本文件?

时间:2011-08-18 12:36:02

标签: java file comparison

关于你对我的“技巧”的看法,我有一个普遍的问题。

有2个文本文件(file_1file_2)需要相互比较。两者都非常庞大(3-4千兆字节,每个30,000,000到45,000,000行)。 我的想法是在内存中读取file_1的几行(尽可能多),然后将这些行与file_2所有行进行比较。如果匹配,则匹配的两个文件中的行应写入新文件。然后继续使用file_1的下一千行,并将这些行与file_2所有行进行比较,直到我完全通过file_1

但这对我来说实际上非常非常耗时且复杂。 你能想到比较这两个文件的任何其他方法吗?

您认为比较可能需要多长时间? 对于我的计划,时间并不重要。我没有使用过如此庞大的文件的经验,因此我不知道这需要多长时间。它不应该超过一天。 ;-)但我担心我的技术会永远存在......

刚出现在我脑海中的另一个问题:你会在内存中读到多少行?越多越好?有没有办法在实际尝试之前确定可能的行数? 我想尽可能多地阅读(因为我认为这更快)但我经常用完内存。

提前致谢。

修改 我想我必须多解释一下我的问题。

目的不是看两个文件是否相同(它们不是)。 每个文件中都有一些共享相同“特征”的行。 这是一个例子: file_1看起来有点像这样:

mat1 1000 2000 TEXT      //this means the range is from 1000 - 2000
mat1 2040 2050 TEXT
mat3 10000 10010 TEXT
mat2 20 500 TEXT

file_2看起来像这样:

mat3 10009 TEXT
mat3 200 TEXT
mat1 999 TEXT

TEXT指的是对我不感兴趣的字符和数字,mat可以来自mat1 - mat50并且没有顺序;也可以有1000x mat2(但下一栏中的数字不同)。我需要以这样的方式找到拟合线:两个比较线中的matX相同,file_2中提到的数字符合file_1中提到的范围。 所以在我的例子中,我会找到一个匹配:file_1的第3行和file_2的第1行(因为mat3和10009都在10000和10010之间)。 我希望这能让你清楚明白!

所以我的问题是:你会如何搜索匹配的行?

是的,我使用Java作为编程语言。

修改 我现在首先将大文件分开,以便我没有内存不足的问题。我还认为比这两个巨大的文件比较(很多)小文件要快得多。之后,我可以按照上面提到的方式对它们进行比较。它可能不是完美的方式,但我还在学习;-) 尽管如此,你所有的方法对我都非常有帮助,谢谢你的回复!

14 个答案:

答案 0 :(得分:2)

在理想的世界中,您可以将file_2的每一行读入内存(可能使用快速查找对象,如HashSet,具体取决于您的需要),然后从file_1中读取每行一次将它与保存file_2中的行的数据结构进行比较。

正如你所说的那样,你的内存不足,我认为分而治之的战略是最好的。您可以使用与我上面提到的相同的方法,但是从file_2读取一半(或三分之一,四分之一......取决于您可以使用多少内存)并存储它们,然后比较所有行在file_1中。然后读入下一半/第三/四分之一/无论进入内存(替换旧行)并再次浏览file_1。这意味着您必须更多地浏览file_1,但是您必须处理内存限制。


编辑:为了回答您问题中添加的详细信息,我将部分更改我的答案。而不是一次读取所有file_2(或在块中)并在file_1中读取一行,而是反过来,因为file_1保存要检查的数据。

另外,关于搜索匹配的行。我认为最好的方法是在file_1上进行一些处理。创建HashMap<List<Range>>,将字符串(“mat1” - “mat50”)映射到Range的列表(只是startOfRange int和endOfRange int的包装器)并使用file_1中的数据填充它。然后编写一个函数(忽略错误检查)

boolean isInRange(String material, int value)
{
    List<Range> ranges = hashMapName.get(material);
    for (Range range : ranges)
    {
        if (value >= range.getStart() && value <= range.getEnd())
        {
            return true;
        }
    }
    return false;
}

并为file_2的每个(已解析的)行调用它。

答案 1 :(得分:2)

我认为,你的方式相当合理。

我可以想象不同的策略 - 例如,您可以在比较之前对两个文件进行排序(其中有高效的filesort实现,unix排序实用程序可以在几分钟内对几个Gbs文件进行排序),并且,在排序后,您可以比较文件随后,逐行阅读。

但这是一个相当复杂的方法 - 您需要运行外部程序(排序),或者自己在java中编写类似的高效文件实现 - 这本身并不是一件容易的事。因此,为了简单起见,我认为你的阅读方式很有前途;

至于如何找到合理的阻挡 - 首先,可能不正确的是“越多越好” - 我认为,所有工作的时间将逐渐增加到一些恒定的线。所以,你可能会比你想的更接近那条线 - 你需要基准。

接下来 - 您可以像这样读取缓冲行:

final List<String> lines = new ArrayList<>();
try{
    final List<String> block = new ArrayList<>(BLOCK_SIZE);
    for(int i=0;i<BLOCK_SIZE;i++){
       final String line = ...;//read line from file
       block.add(line);
    }
    lines.addAll(block); 
}catch(OutOfMemory ooe){
    //break
}

所以你可以尽可能多地阅读 - 留下最后一个BLOCK_SIZE的空闲内存。对于你们其余的程序来说,BLOCK_SIZE应该很大,而不用OOM运行

答案 2 :(得分:1)

有一个权衡:如果您阅读了大部分文件,则保存光盘seek time,但您可能已经阅读了不需要的信息,因为在第一行遇到了更改。 / p>

你可能应该运行一些具有不同块大小的实验[基准测试],以找出在一般情况下读取的最佳块。

答案 3 :(得分:1)

我从未使用过如此庞大的文件,但这是我的想法,应该可行。

你可以看看哈希。使用SHA-1哈希。

导入以下内容

import java.io.FileInputStream;
import java.security.MessageDigest;

一旦加载了文本文件等,它就会循环遍历每一行,最后打印出散列。下面的示例链接将更深入。

StringBuffer myBuffer = new StringBuffer("");
//For each line loop through
    for (int i = 0; i < mdbytes.length; i++) {
        myBuffer.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
    }
System.out.println("Computed Hash = " + sb.toString());

SHA Code example focusing on Text File

SO Question about computing SHA in JAVA (Possibly helpful)

Another sample of hashing code.

简单读取每个文件seperatley,如果每个文件的哈希值在进程结束时相同,那么这两个文件是相同的。如果没有,则出现问题。

然后,如果你得到一个不同的值,你可以逐行检查超级耗时。

总的来说,似乎逐行逐行阅读将需要永远。如果你想找到每个人的差异,我会这样做。但我认为哈希会更快看到它们是否相同。

SHA checksum

答案 4 :(得分:1)

不确定答案会有多好 - 但请看一下这个页面:http://c2.com/cgi/wiki?DiffAlgorithm - 它总结了一些差异算法。 Hunt-McIlroy算法可能是更好的实现。从该页面还有一个指向GNU diff的java实现的链接。但是,我认为在C / C ++中实现并编译成本机代码会更快。如果你坚持使用java,你可能需要考虑JNI。

答案 5 :(得分:1)

确实,这可能需要一段时间。您必须进行1,200.000,000行比较。 有几种可能性可以加快这一速度:

一种方法是对文件2进行排序,并在文件级别进行二进制搜索。 另一种方法:计算每一行的校验和,然后搜索。根据平均线路长度,相关文件会小得多,如果以固定格式存储校验和(即很长),你真的可以进行二进制搜索

然而,您从file_1一次读取的行数很重要。面对极大的复杂性,这是微观优化。

答案 6 :(得分:1)

如果您想要一个简单的方法:您可以散列两个文件并比较散列。但是使用你的方法可能更快(特别是如果文件不同)。关于内存消耗:只要确保你使用足够的内存,使用没有缓冲区这样的东西是个坏主意..

所有关于哈希,校验和等的答案:那些并不快。在这两种情况下,您都必须阅读整个文件。使用哈希/校验和,你甚至需要计算一些东西......

答案 7 :(得分:1)

您可以做的是对每个单独的文件进行排序。例如Java中的UNIX sort或类似内容。您可以一次读取一行的已排序文件以执行合并排序。

答案 8 :(得分:1)

如果您想确切地知道文件是否不同,那么没有比您更好的解决方案 - 顺序比较。

但是,如果文件相同,您可以使用某种启发式方法告诉您某种可能性。 1)检查文件大小;这是最简单的。 2)取一个随机文件位置并比较两个文件中此位置开始的字节块。 3)重复步骤2)以达到所需的概率。

您应该计算并测试有多少读取(以及块的大小)对您的程序有用。

答案 9 :(得分:1)

我的解决方案是首先生成一个文件的索引,然后使用它来进行比较。这类似于其他一些答案,因为它使用散列。

你提到线路数量高达约4500万。这意味着您可以(可能)存储一个索引,每个条目使用16个字节(128位),它将使用大约45,000,000 * 16 = ~685MB的RAM,这在现代系统上并不合理。使用我在下面描述的解决方案有一些开销,因此您可能仍然发现需要使用其他技术(如内存映射文件或基于磁盘的表)来创建索引。有关如何将索引存储在基于磁盘的快速哈希表中的示例,请参阅HypertableHBase

因此,完整地,算法将类似于:

  1. 创建一个将Long映射到Longs列表的哈希映射(HashMap&lt; Long,List&lt; Long&gt;&gt;)
  2. 获取第一个文件中每行的哈希值(Object.hashCode应该足够)
  3. 获取该行文件中的偏移量,以便稍后再次找到它
  4. 将偏移量添加到哈希映射中具有匹配hashCodes的行列表
  5. 将第二个文件的每一行与索引
  6. 中的行偏移量进行比较
  7. 保留任何具有匹配条目的行
  8. 修改 在回答您编辑过的问题时,这本身并没有多大帮助。你可以只哈希该行的第一部分,但它只会创建50个不同的条目。然后,您可以在数据结构中创建另一个级别,这会将每个范围的开头映射到它来自的行的偏移​​量。

    index.get("mat32")之类的东西会返回范围的TreeMap。您可以查找要查找的值lowerEntry()之前的范围。这将使您快速检查给定的matX /数字组合是否在您正在检查的范围之一。

答案 10 :(得分:1)

现在你已经给了我们更多的细节,我将采取的方法依赖于预分区,并可选择在搜索匹配之前进行排序。

这应该消除大量的比较,否则这些比较在幼稚的暴力方法中无论如何都是不匹配的。为了论证,让我们将这两个文件固定在每个4000万行。

分区:通读file_1并将所有以mat1开头的行发送到file_1_mat1,依此类推。对file_2执行相同操作。这有点grep是微不足道的,或者如果您希望以Java编程方式进行,那么这是初学者的练习。

这是一次通过两个文件,总共读取了8000万行,产生了两组50个文件,平均每个800,000行。

排序:对于每个分区,只根据第二列中的数值(file_1的下限和file_2的实际数字)进行排序。即使800,000行不能适应内存,我想我们可以调整双向外部合并排序,并且比整个未分区空间更快地执行此操作(总读取次数更少)。

比较:现在您只需通过两对file_1_mat1file_2_mat1迭代一次,而无需在内存中保留任何内容,输出匹配到输出文件。依次重复其余分区。无需最终的“合并”步骤(除非您并行处理分区)。

即使没有排序阶段,您已经在做的天真比较应该在50对文件中更快地运行,每个文件有800,000行,而不是两个文件,每个文件有4000万行。

答案 11 :(得分:0)

尽量避免使用内存并使其耗费光盘。 我的意思是将每个文件分成可加载大小的部分并进行比较,这可能需要一些额外的时间,但会让你安全地处理内存限制。

答案 12 :(得分:0)

如何使用像Mercurial这样的源代码管理?我不知道,也许它不是你想要的,但这是一个旨在跟踪修订之间变化的工具。您可以创建一个存储库,提交第一个文件,然后用另一个文件覆盖它,然后提交第二个文件:

hg init some_repo
cd some_repo
cp ~/huge_file1.txt .
hg ci -Am "Committing first huge file."
cp ~/huge_file2.txt huge_file1.txt
hg ci -m "Committing second huge file."

从这里你可以得到一个差异,告诉你哪些线条不同。如果你能以某种方式使用那个差异来确定哪些行是相同的,那么你就可以了。

这只是一个想法,如果我错了,有人会纠正我。

答案 13 :(得分:0)

我会尝试以下操作:对于您要比较的每个文件,在磁盘上创建临时文件(我将其称为部分文件),表示每个字母和其他所有字符的附加文件。然后逐行读取整个文件。在执行此操作时,将该行插入与其开头的字母对应的相关文件中。既然你已经为这两个文件做了这些,你现在可以限制比较一次加载两个较小的文件。例如,以A开头的行只能出现在一个部分文件中,并且不需要多次比较每个部分文件。如果生成的文件仍然非常大,则可以通过根据文件中的第二个字母创建文件,对生成的部分文件(特定于字母的文件)应用相同的方法。这里的交易将暂时使用大磁盘空间,直到该过程结束。在此过程中,此处其他帖子中提到的方法可以帮助更有效地处理部分文件。