包含不同但相似字符串的对象的Java哈希码()冲突

时间:2015-03-26 11:37:50

标签: java string collision hashcode

在验证程序的输出数据时,我确定了两个不同对象的哈希码相同的情况。为了获得这些代码,我使用了以下函数:

int getHash( long lID, String sCI, String sCO, double dSR, double dGR, String sSearchDate ) {

    int result = 17;
    result = 31 * result + (int) (lID ^ (lID >>> 32));
    long temp;
    temp = Double.doubleToLongBits(dGR);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(dSR);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    result = 31 * result + (sCI != null ? sCI.hashCode() : 0);
    result = 31 * result + (sCO != null ? sCO.hashCode() : 0);
    result = 31 * result + (sSearchDate != null ? sSearchDate.hashCode() : 0);

    return result;
}

这是两个例子:

getHash( 50122,"03/25/2015","03/26/2015",4.0,8.0,"03/24/15 06:01" )
getHash( 51114,"03/24/2015","03/25/2015",4.0,8.0,"03/24/15 06:01" )

我想,这个问题出现了,因为我的数据中存在三个非常相似的字符串,字符串A到B和B到C之间的哈希码的差异大小相同,导致返回的哈希码相同。

IntelliJ提议的hashcode()实现使用31作为每个变量的乘数,这些变量有助于最终的哈希码。我想知道为什么一个人没有为每个变量使用不同的值(比如33,37,41(我在其他帖子中提到过处理哈希码时已提到))?在我的情况下,这将导致我的两个对象之间的区别。

但我想知道这是否会导致其他情况下出现问题?

有关此的任何想法或提示吗?非常感谢你!

3 个答案:

答案 0 :(得分:5)

hashCode()契约允许不同的对象具有相同的哈希码。来自documentation

  

不要求如果两个对象根据equals(java.lang.Object)方法不相等,则在两个对象中的每一个上调用hashCode方法必须产生不同的整数结果。但是,程序员应该知道为不等对象生成不同的整数结果可能会提高哈希表的性能。

但是,既然你的哈希有很多参数,你可以考虑使用Objects.hash()而不是自己实现:

@Override
int getHash(long lID, String sCI, String sCO, double dSR, double dGR, String sSearchDate) {
    return Objects.hash(lID, sCI, sCO, dSR, dGR, sSearchDate);
}

例如:

Objects.hash(50122, "03/25/2015", "03/26/2015", 4.0, 8.0, "03/24/15 06:01")
Objects.hash(51114, "03/24/2015", "03/25/2015", 4.0, 8.0, "03/24/15 06:01")

结果:

-733895022
-394580334

答案 1 :(得分:1)

您显示的代码可以添加零,例如

result = 31 * result + (sCI != null ? sCI.hashCode() : 0);

当添加一些零时,这可能会退化为多个

31 * 31 * 31 ...

可能会破坏唯一性。

但是,hashCode方法不是为了返回唯一值。它应该提供统一的值分布,并且应该很容易计算(或者像String类一样缓存hashCode)。

从更理论的角度来看,hashCode从大集合A映射到较小的集合B.因此,碰撞(从A映射到B中的相同值的不同元素)是不可避免的。你可以选择一个大于A的集合B,但这会违反hashCode的目的:性能优化。实际上,您可以使用链接列表和使用hashCode实现的一些额外逻辑来实现任何目标。

选择素数,因为它们可以带来更好的分布。例如,如果使用无素数,则​​4 * 3 = 12 = 2 * 6会产生相同的hashCode。 31有时被选为梅森素数2 ^ n-1,据说在处理器上表现更好(我不确定)。

由于指定了hashCode方法,因此不能明确地返回标识非唯一hashCodes的元素。假设hashCodes的唯一性是一个错误。

然而,HashMap可以被描述为一组桶,每个桶包含一个链接的元素列表。存储桶由hashCode索引。因此,提供相同的hashCodes可以减少列表较长的桶数。在最极端的情况下(将任意常量作为hashCode返回),映射会退化为链表。

在散列数据结构中搜索对象时,hashCode用于获取存储区索引。对于此存储桶中的每个对象,调用equals方法 - > long列表意味着大量的equals调用。

结论:假设正确使用了hashCode方法,这不会导致程序出现故障。但是,它可能会导致严重的性能损失。

答案 2 :(得分:1)

Ash其他答案解释得很清楚,hashCode允许为不同的对象返回相同的值。这不是加密哈希值,因此很容易找到hashCode冲突的示例。

但是,我在您的代码中指出了一个问题:如果您自己制作了hashCode方法,那么您肯定应该使用更好的哈希算法。看看MurmurHash:http://en.wikipedia.org/wiki/MurmurHash。您想使用32位版本。还有Java implementations

是的,哈希冲突可能会导致性能问题。因此,使用良好的哈希算法很重要。此外,为了安全起见,MurmurHash允许种子值使哈希冲突拒绝服务攻击更加困难。您应该生成在程序开始时随机使用的种子值。您对hashCode方法的实现容易受到这些哈希冲突DoS攻击。