【问题标题】:Java hash collision probabilityJava 哈希冲突概率
【发布时间】:2013-12-21 14:35:03
【问题描述】:

我将大量对象(对象的字节数组中存储的值的唯一组合)存储在哈希映射(约 280 万个对象)中,并且在检查我是否有哈希码冲突时(32 -bit hash),我很惊讶地发现没有,而在统计上,我有几乎 100% 的机会发生至少一次碰撞(参见http://preshing.com/20110504/hash-collision-probabilities/)。

因此,我想知道我检测碰撞的方法是否有问题,或者我是否非常幸运......

这是我尝试从地图中存储的 280 万个值中检测碰撞的方法:

HashMap<ShowdownFreqKeysVO, Double> values;
(...fill with 2.8 mlns unique values...)
HashSet<Integer> hashes = new HashSet<>();
for (ShowdownFreqKeysVO key:values.keySet()){
    if (hashes.contains(key.hashCode())) throw new RuntimeException("Duplicate hash for:"+key);
    hashes.add(key.hashCode());
}

这是对象创建哈希值的方法:

public class ShowdownFreqKeysVO {
    //Values for the different parameters
    public byte[] values = new byte[12];

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Arrays.hashCode(values);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ShowdownFreqKeysVO other = (ShowdownFreqKeysVO) obj;
        if (!Arrays.equals(values, other.values))
            return false;
        return true;
    }
}

任何关于我做错了什么的想法/提示将不胜感激!

谢谢, 托马斯

【问题讨论】:

  • hashes 在这行 HashSet&lt;Integer&gt; hashes = new HashSet&lt;&gt;(); 之后包含什么?您如何填充 hashes 的值?
  • 他在循环中添加了hashes.add(key.hashCode());。
  • @mprivat 这只是实现 hashCode 的常见样板。
  • 这对我来说是正确的。检查该集合是否包含 280 万个元素:(...fill with 2.8 mlns unique values...) 中可能有问题。或者你很幸运。我也会简单地使用return Arrays.hashCode(values):在这个 hashCode 中添加 31 是不必要的。
  • @LeosLiterak:您应该阅读有关生日悖论的内容 (en.wikipedia.org/wiki/Birthday_problem)。在只有 23 人的人口中,有 50% 的概率有两个人在一年中的同一天出生(尽管 23 比 365 少得多)。而且我们这里说的不是 2^(12*8),而是 2^32(可能的 int hashCodes 的数量)

标签: java hash hashcode hash-collision


【解决方案1】:

我不相信运气

这是您使用的Arrays.hashCode 的实现

public static int hashCode(int a[]) {
    if (a == null)
        return 0;

    int result = 1;
    for (int element : a)
        result = 31 * result + element;

    return result;
}

如果您的值恰好小于 31,它们将被视为以 31 为基数的不同数字,因此每个结果都会产生不同的数字(如果我们暂时忽略溢出)。让我们称之为纯哈希

当然,31^11 比 Java 中的整数数量要大得多,所以我们会遇到大量溢出。但是由于 31 的幂和最大整数是“非常不同的”,所以你不会得到一个几乎随机的分布,而是一个非常规则的均匀分布。

让我们考虑一个更小的例子。我假设您的数组中只有 2 个元素,每个元素的范围从 0 到 5。我尝试通过取“纯哈希”的模 38 来创建 0 到 37 之间的“hashCode”结果是我得到了 5 个整数的条纹,它们之间有小间隙,而不是一次碰撞。

val hashes = for {
  i <- 0 to 4
  j <- 0 to 4
} yield (i * 31 + j) % 38

println(hashes.size) // prints 25
println(hashes.toSet.size) // prints 25

要验证您的号码是否会发生这种情况,您可以创建一个图表,如下所示: 对于每个散列,将前 16 位用于 x,将后 16 位用于 y,将点涂成黑色。我打赌你会看到一个非常规则的模式。

【讨论】:

  • 谢谢!实际上,存储在字节数组中的所有值的值都低于 31(它们的范围在 -1 和 15 之间)
【解决方案2】:

我认为您的代码没有任何问题,但是您链接到的分析假设 hashCodes 是均匀分布的,并且不同对象的 hashCodes 是独立的随机变量。

后者可能不正确:您知道对象是唯一的(因此不是独立的)。也许 hashCode 函数保留了特定品牌的独特性。

【讨论】:

    猜你喜欢
    • 2020-10-21
    • 2017-09-28
    • 2010-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    • 1970-01-01
    • 2023-03-03
    相关资源
    最近更新 更多