【问题标题】:Best method of making a hash function制作散列函数的最佳方法
【发布时间】:2013-11-17 00:40:30
【问题描述】:

我正在为 java 中的哈希映射构建一个复合键,并希望为这些对象中的每一个确定我自己的哈希码。我的问题是以下两种方法中最好的方法是什么。我的复合键具有三个 String 属性和一个 int 属性。

public int hashCode(){
    return (className + methodName + uniqueNumber).hashCode();
}

public int hashCode(){
    return (className + methodName + desc + uniqueNumber).hashCode();
}

我必须有 className、methodName 和唯一编号,以保证每个键都有唯一的哈希码。我想采用碰撞机会最小的方法。我的直觉是,我“添加”到哈希映射函数的属性越多,发生冲突的可能性就越小。但是,我并不完全确定这是正确的。

【问题讨论】:

  • 相信在SO上还有很多类似的问题......
  • 如果要保证没有冲突,return uniqueNumber;
  • uniqueNumber 是一个持续递增的数字,我无法直接控制其值。仅使用 uniqueNumber 将生成唯一的哈希值,但我将失去在哈希图中引用特定值的能力
  • 这个哈希码是干什么用的?通常是从对象的某些字段生成的,应该和equals()的实现一致
  • @user2736423:您在equals 中使用了哪些字段?如果uniqueNumber 在其中,在hashCode 中使用它就完成了。否则,您不得hashCode 中使用它,否则将不再有用。

标签: java performance hash hash-collision


【解决方案1】:

您的问题有点不清楚,至于您需要/足以唯一区分密钥的字段。

通常,您应该通过乘以素数来组合单个哈希(在复合键中)。

假设第一个例子:

public int hashCode() {
    int h = className.hashCode() * 23;
    h += methodName.hashCode() * 17;
    h += uniqueNumber;
    return h;
}

OTOH 如果 uniqueNumber 实际上是唯一的,您可以简化:

public int hashCode() {return uniqueNumber;}

在您的评论中,您提到了一件事:“仅使用 uniqueNumber 将生成唯一的哈希值,但我将失去在哈希图中引用特定值的能力”。

现在这很重要:“实例身份”是一个非常不同的哈希和查找的东西,与“价值”!您不能对两者使用相同的哈希码和映射。

例如,如果您需要一个 Key(ClassName, MethodName) -> SomeValue 查找,这将是一个“值”查找,并且需要通过 ClassName 和 MethodName 值进行散列,以便可以重复:即,所以您可以为 Map.get() 构造一个键来执行查找。

“Instance Identity”实际上在 Java 中内置了对哈希和映射的支持——它被称为 IdentityHashMap。

但在大多数情况下,尤其是复合键(可能用于地图),需要能够重新构造键以便稍后执行查找。所以键应该有值语义,你的uniqueNumber是否真的应该是键的一部分是值得怀疑的。

稍后你去查找时,如何获取正确的uniqueNumber 来检索数据?我的感觉是:

  1. 要么应该有一个一流的实体,您可以直接将其用作键(因此不再需要 CompositeKey 类),要么

  2. 您无法重复获取uniqueNumber,在这种情况下它不起作用/无论如何都不需要。

总结一下:如果uniqueNumber 确实需要或完全适用,我希望它已经被封装在一个一流的实体中。事实并非如此。看起来您很可能应该使用 基于值的键,并删除 uniqueNumber 位(至少从这里开始)。

所以我的建议:

public int hashCode() {
    int h = className.hashCode() * 23;
    h += methodName.hashCode() * 17;
    h += desc.hashCode();
    return h;
}

如果这有帮助,请告诉我。

【讨论】:

  • 正确。我没有举最好的例子。为了简化我的问题,使用对象的更多属性来计算哈希码是否会降低冲突的机会
  • 没有。一旦你有足够的字段来形成“主键”或“唯一键”,将它们与不同的主因子相乘,你将拥有一个合理的低冲突哈希码。对于基于值的复合键,只需 classNamemethodName 就足够且正确。
  • 这确实有帮助。 uniqueNumber 用于计算其他一些值,我认为将它包含在我的函数中会降低发生碰撞的可能性,但我以后无法恢复这个数字。但是,className 和 methodName 本身并不是键所独有的。这里的部分问题是我完全扼杀了我的问题的解释。我没有在我的 equals 方法中使用 uniqueNumber,因为我无法恢复它,但我使用的是 desc,因为带有 className 和 methodName 的 desc 会产生一个唯一键。我提到了 className、methodName 和 uniqueNumber 作为
  • 键,因为这些属性一起是唯一的(我的代码在过去 20 分钟内一直在变化,这就是为什么我的问题表述不清楚的原因)。考虑到这一点,我认为我的问题已经变成了,在我的哈希码计算中包含 uniqueNumber 是否会降低冲突的机会。
  • 好的,谢谢,这是有道理的。我唯一的评论是指出您的建议不会是唯一的(也需要添加 desc)。这可能没有意义,但在一个类中有多个构造函数的情况下确实如此。在这种情况下,将有两个具有相同名称的方法,因此需要 desc 创建唯一性。谢谢你的帮助:)。
【解决方案2】:

几厘米;

(1) 哈希码不必是唯一的。事实上,它们通常不能保证是唯一的。在大多数情况下,保证唯一性的计算成本太高,也不可取。碰撞不是灾难性的。

(2) 哈希码应该反映对象实例的状态,而不是对象类。类名之类的东西不会进入它。当然,除非那是类的实例数据,例如在代表堆栈跟踪的一帧的类中。

(3) 一个好的哈希码会有大量可能的值,这些值会按概率分布,因此不太可能发生冲突。

(4) 在Java中,哈希码必须与Object.equals()一致。请参阅 java.lang.Object 的 Javadoc 以供参考。

【讨论】:

  • className 是我的密钥的一个属性(我正在使用 ASM),因此 className 在区分密钥方面很重要。我的问题是使用更多的值来计算哈希码是否会降低冲突的机会,或者它是否没有显着差异。
  • 一旦您在哈希码中获得了主要的主键字段,您将获得哈希码的良好区分度。对于字节码域,我肯定会同时使用classNamemethodName——否则同一个类中的每个方法都会发生哈​​希冲突。
  • 如果它们不是多余的,使用更多的值会降低碰撞的几率。我也同意 Thomas 的观点,您可能希望同时使用 className 和 methodName。其他任何事情都可能不会好很多。
  • 还有一件事,要明确一点:是的,使用主键字段是一个很好的起点,但只有自然键是好的。代理键已出。如果它来自数据库序列或标识列,那就有问题了。出于同样的原因,您上面提到的 uniqueNumber 很可能是麻烦。哈希应该只依赖于自然数据以避免出现问题。
猜你喜欢
  • 1970-01-01
  • 2021-05-21
  • 1970-01-01
  • 2014-01-15
  • 2010-11-24
  • 1970-01-01
  • 2012-01-20
  • 1970-01-01
  • 2013-11-06
相关资源
最近更新 更多