2

为避免任何混淆,我将根据我对哈希算法的研究重新提出我的问题

问题陈述 我有多个包含可变长度数据记录的文本文件。我需要查找输入中是否有重复记录。每个文本文件都可能有数百万的数据记录。

由于我无法一次将所有数据加载到内存中,因此我计划在处理记录时创建记录中关键字段的哈希。处理记录意味着验证、过滤和转换它。处理完所有文本文件中的所有记录后,将它们合并以创建整个输入的一个视图(文本文件或数据库表)。

如果为所有记录生成哈希值,则查找重复项会快得多。如果散列值存在冲突,则只能检查那些记录是否相等(假设散列算法是确定性的)

问题 - 对于此类输入(即可变长度数据),我应该考虑哪些哈希算法?

4

2 回答 2

4

简答

不要这样做。使用 Java 映射。您可以在此处找到详细信息:http: //docs.oracle.com/javase/6/docs/api/java/util/Map.html

长答案

您可以通过将字符串视为 base-N 中的数字来创建完美的散列函数,其中 N 是任何字符可以采用的所有可能值。这里的问题是内存。散列函数旨在与数组一起使用,这意味着您需要一个足够大的数组来处理散列结果,这是不切实际的。

例如,以 10 个字符的键为例。让我们更加谦虚,假设它们保证只包含小写字母。这为每个字符和 10 个字符提供了 26 种可能性。这意味着可能的组合是:

26 ^ 10 = 141,167,095,653,376

如果您查看散列算法,它们首先包含的内容之一就是碰撞检测,因为它们承认碰撞是生活中的事实。

现在你说你没有在内存中加载键,但你为什么要使用哈希呢?哈希的目的是为您提供到数组索引的映射。也许你最好使用另一种机制。

可能的解决方案

如果您担心内存,请获取有关文件中重复项的一些统计信息。如果您只存储一个标志来指示哈希中特定键的出现,并且您有很多重复项,那么您也许可以只使用 Java 的映射。Java 的映射处理冲突,因此不会阻止您检测唯一键。您可以放心,如果找到 A[x],则意味着 x 在 A 中,即使 x 的哈希与先前的哈希冲突。

接下来,您可以尝试一些实用程序来提取重复项。由于它们是专门为此目的而编写的,因此它们应该能够处理大量数据。

最后,您可以尝试将您的条目放入数据库并使用它来处理重复项。这可能看起来有点矫枉过正,但数据库已针对处理大量记录进行了优化。

于 2012-12-17T13:28:24.630 回答
1

这是 Map 理念的扩展。在诉诸于此之前,我会检查它不能通过简单地构建一个代表所有字符串的 HashSet 来完成。请记住,您可以使用 64 位 JVM 并设置较大的堆大小。

定义一个 StringLocation 类,其中包含对磁盘上的字符串进行随机访问所需的数据 - 例如,对 RandomAcessFile 的引用和文件内的偏移量。如果您不能一次打开所有文件,请根据需要打开和关闭,为最常用的文件缓存 RandomAcessFile。

创建一个HashMap<Integer,List<StringLocation>>.

开始阅读字符串。对于每一个字符串,转换为小写,得到它的hashCode(),hash,整数形式。如果 Map 中有一个以哈希为键的条目,则将新字符串与现有值中表示的每个字符串进行比较,执行随机文件访问以获取已处理的字符串。使用字符串 equalsIgnoreCase。如果有匹配项,则您有重复项。如果没有匹配项,则将表示当前字符串的新 StringLocation 附加到 List。

这需要一次最多两个字符串在内存中,一个是您当前正在处理的字符串,另一个是先前处理的字符串,它具有您要与之比较的相同 hashCode() 结果。

通过使用 MessageDigest 为小写字符串生成具有低冲突风险的宽校验和并将其保存在 StringLocation 对象中,您可以进一步减少必须重新读取字符串以进行等式检查的次数。在比较期间,如果校验和不匹配,则返回 false,无需重新读取字符串。

于 2012-12-17T14:41:42.463 回答