我们如何决定集合的hashCode()方法的最佳实现(假设equals方法已被正确重写)?


当前回答

当组合哈希值时,我通常使用boost c++库中使用的组合方法,即:

seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);

这在确保平均分配方面做得相当好。有关这个公式如何工作的一些讨论,请参阅StackOverflow的帖子:boost::hash_combine中的魔术数字

在http://burtleburtle.net/bob/hash/doobs.html上有关于不同哈希函数的很好的讨论

其他回答

下面是另一个考虑超类逻辑的JDK 1.7+方法演示。我认为它对对象类hashCode()进行记帐非常方便,纯粹依赖于JDK,没有额外的手工工作。请注意Objects.hash()是空容忍的。

我没有包括任何equals()实现,但实际上您当然需要它。

import java.util.Objects;

public class Demo {

    public static class A {

        private final String param1;

        public A(final String param1) {
            this.param1 = param1;
        }

        @Override
        public int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param1);
        }

    }

    public static class B extends A {

        private final String param2;
        private final String param3;

        public B(
            final String param1,
            final String param2,
            final String param3) {

            super(param1);
            this.param2 = param2;
            this.param3 = param3;
        }

        @Override
        public final int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param2,
                this.param3);
        }
    }

    public static void main(String [] args) {

        A a = new A("A");
        B b = new B("A", "B", "C");

        System.out.println("A: " + a.hashCode());
        System.out.println("B: " + b.hashCode());
    }

}

如果我正确理解你的问题,你有一个自定义的集合类(即一个从集合接口扩展的新类),你想实现hashCode()方法。

如果您的集合类扩展了AbstractList,那么您就不必担心它,因为已经有equals()和hashCode()的实现,它通过遍历所有对象并将它们的hashCodes()相加来工作。

   public int hashCode() {
      int hashCode = 1;
      Iterator i = iterator();
      while (i.hasNext()) {
        Object obj = i.next();
        hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
      }
  return hashCode;
   }

现在,如果你想要的是计算特定类哈希码的最佳方法,我通常使用^(按位排他或)操作符来处理我在equals方法中使用的所有字段:

public int hashCode(){
   return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}

只是一个快速的注释,以完成其他更详细的答案(在代码方面):

如果我考虑如何在java中创建哈希表的问题,特别是jGuru FAQ条目,我相信可以判断哈希代码的其他标准是:

同步(算法是否支持并发访问)? 失败安全迭代(算法是否检测到迭代过程中发生变化的集合) 空值(哈希码是否支持集合中的空值)

我更喜欢使用实用工具方法从谷歌集合库从类对象,帮助我保持我的代码干净。equals和hashcode方法通常都是从IDE的模板中创建的,所以它们的可读性不太好。

标准实现很弱,使用它会导致不必要的冲突。想象一个

class ListPair {
    List<Integer> first;
    List<Integer> second;

    ListPair(List<Integer> first, List<Integer> second) {
        this.first = first;
        this.second = second;
    }

    public int hashCode() {
        return Objects.hashCode(first, second);
    }

    ...
}

Now,

new ListPair(List.of(a), List.of(b, c))

and

new ListPair(List.of(b), List.of(a, c))

List的乘数具有相同的hashCode,即31*(a+b) + c。hashCode在这里被重用。显然,碰撞是不可避免的,但产生不必要的碰撞只是……不必要的。

There's nothing substantially smart about using 31. The multiplier must be odd in order to avoid losing information (any even multiplier loses at least the most significant bit, multiples of four lose two, etc.). Any odd multiplier is usable. Small multipliers may lead to faster computation (the JIT can use shifts and additions), but given that multiplication has latency of only three cycles on modern Intel/AMD, this hardly matters. Small multipliers also leads to more collision for small inputs, which may be a problem sometimes.

使用质数是没有意义的,因为质数在环Z/(2**32)中没有意义。

因此,我建议使用随机选择的大奇数(可以选择质数)。由于i86/amd64 cpu可以使用更短的指令来匹配一个有符号字节的操作数,因此对于像109这样的乘法器来说,速度优势很小。为了最小化冲突,可以使用类似0x58a54cf5的值。

在不同的地方使用不同的乘数是有帮助的,但可能不足以证明额外的工作是合理的。