【问题标题】:Java hashCode for a Point classPoint 类的 Java hashCode
【发布时间】:2012-02-26 11:46:19
【问题描述】:

我有一个简单的自定义 Point 类,如下所示,我想知道我的 hashCode 实现是否可以改进,或者这是否是最好的。

public class Point 
{
    private final int x, y;

    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public int getX() 
    {
        return x;
    }

    public int getY()
    {
        return y;
    }

    @Override
    public boolean equals(Object other) 
    {
        if (this == other)
          return true;

        if (!(other instanceof Point))
          return false;

        Point otherPoint = (Point) other;
        return otherPoint.x == x && otherPoint.y == y;
    }


    @Override
    public int hashCode()
    {
        return (Integer.toString(x) + "," + Integer.toString(y)).hashCode();
    }

}

【问题讨论】:

  • 您打算如何改进它?你想尝试让它更快吗?
  • 您想保证唯一性吗?速度?
  • 我想同时保证:)

标签: java hashcode hash-code-uniqueness


【解决方案1】:

将二维点散列为单个整数的一种非常好的方法是使用数字螺旋!

http://ulamspiral.com/images/IntegerSpiral.gif

@Override
public int hashCode() {
    int ax = Math.abs(x);
    int ay = Math.abs(y);
    if (ax>ay && x>0) return 4*x*x-3*x+y+1;
    if (ax>ay && x<=0) return 4*x*x-x-y+1;
    if (ax<=ay && y>0) return 4*y*y-y-x+1;
    return 4*y*y-3*y+x+1;
}

虽然此方法需要更多计算,但不会出现不可预知的冲突。它还有一个很好的特性,即越靠近原点的点通常具有较小的哈希值。 (仍然可以用 x 或 y > sqrt(MAX_VALUE) 溢出)

【讨论】:

    【解决方案2】:

    请不要使用字符串。这背后有很多理论和几种实现(除法方法,乘法一等......)。如果您有大约一个小时的时间,您可以观看此MIT-Class

    话虽如此,以下是 Netbeans 7.1 的建议:

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 71 * hash + this.x;
        hash = 71 * hash + this.y;
        return hash;
    }
    

    2015 年 10 月编辑

    我不久前开始使用 IntelliJ,现在我过得更开心了。这就是它的自动 hashCode 生成产生的结果。它不那么冗长。还要注意素数的使用。

    @Override
    public int hashCode() {
        int result = x;
        result = 31 * result + y;
        return result;
    }
    

    【讨论】:

    • +1 有趣的是,Netbeans 提出的建议与 eclipse 不同,但实现的基础是相似且可靠的。
    • 我很困惑,第二个实现如何是一个好的实现?即使是非常小的数字,例如 (0,31),(1,0),也会发生冲突。这似乎很有害,不是吗?
    • @ChristopherShroba 你的评论很有趣,我会在假期回来后研究一下!主要问题是result 在给定示例输入的情况下使用0 进行初始化。尽管如此,这就是 IntelliJ 2016 所做的......
    • 对,对于 (a, 31b),(b,31a) 形式的任何点都会发生碰撞。感谢您的回复!当您有机会时,我很想听听您对此的看法!
    • @ChristopherShroba 我认为问题是由于结果被初始化为 0 的假设也是错误的......但是,Netbeans 实现也不能免受(a, 71b), (b,71a) 攻击......想法?
    【解决方案3】:

    我建议使用一种更简单、性能更高的不带字符串的方法,也许是来自 this answer 的 Josh Bloch 的方法,就您而言:

    return 37 * x + y;
    

    编辑:nybbler 是正确的。实际推荐的是:

    int result = 373; // Constant can vary, but should be prime
    result = 37 * result + x;
    result = 37 * result + y;
    

    【讨论】:

    • 这不是您链接答案中推荐的内容。您错过了应该单独而不是同时为每个字段完成此操作。该算法为 [0,37] 和 [1,0] 生成相同的结果
    • 请注意,新的实现仍然会为 (x, 37y), (y, 37x) 形式的任何点对生成碰撞...
    【解决方案4】:

    Gevorg 建议的所有重要成员字段的值的手动乘法可能是最有效的并且具有良好的值分布。但是,如果您喜欢可读性,Java 7 中也有不错的替代方案...

    import java.util.Objects;
    
    ...
    
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
    

    ...或在Guava 库中:

    import com.google.common.base.Objects;
    
    ....
    
    @Override
    public int hashCode() {
        return Objects.hashCode(x, y);
    }
    

    这两个 varags 方法都只是简单地委托给 Arrays.hashCode(Object[] a),因此由于整数的自动装箱和创建对象引用数组而对性能有轻微影响,但它应该远没有使用反射那么重要。

    而且可读性非常好,因为您很容易看到,哪些字段用于哈希码计算,所有乘法和加法语法都隐藏在 Arrays.hashCode(Object[] a) 的幕后:

    public static int hashCode(Object a[]) {
        if (a == null)
            return 0;
    
        int result = 1;
    
        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());
    
        return result;
    }
    

    【讨论】:

    • 仍然容易受到(x, 31y), (y, 31x) 形式的任何对的攻击,例如(0, 31), (1, 0) 或 (3, 217), (7, 93)。我试图在这里引发广泛的讨论。有没有办法有一个更健壮的实现,或者只有 2 个整数必须处理这种问题(这取决于哈希码生成中使用的素数)?
    【解决方案5】:

    默认情况下,Eclipse 将为您的 Point 类使用 hashCode() 函数,类似于:

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

    至少,在你的 hashCode 算法中加入一个素数将有助于它的“唯一性”。

    【讨论】:

    • 也许你写错了“Java”而不是“Eclipse”。默认情况下,hashCode“通常通过将对象的内部地址转换为整数来实现。”
    • @MatthewFlaschen 确实我做到了。现已更新;感谢您了解。
    【解决方案6】:

    我曾经编写自己的 hash 和 equals 函数,然后我发现了这个:)

    import org.apache.commons.lang.builder.HashCodeBuilder;
    import org.apache.commons.lang.builder.EqualsBuilder;
    
    @Override
    public boolean equals(Object obj) {
       return EqualsBuilder.reflectionEquals(this, obj);
     }
    @Override
    public int hashCode() {
       return HashCodeBuilder.reflectionHashCode(this);
     }
    

    当然要记住以下几点:

    因为反射涉及动态解析的类型, 无法执行某些 Java 虚拟机优化。 因此,反射操作的性能比它们的 非反射对应物,应在代码部分中避免 在性能敏感的应用程序中经常调用它们。 SRC

    【讨论】:

    • 由于只有两个字段,你也可以使用这个库,但明确列出字段。
    • 如果这个类在 Collection 中被大量使用,反射 hashCode 将会对性能造成很大的影响。
    • 我建议改用HashCodeBuilder.append 方法。
    【解决方案7】:

    您可以查看现有的 Point 类型类实现:

    /**
    343      * Returns the hashcode for this <code>Point2D</code>.
    344      * @return a hash code for this <code>Point2D</code>.
    345      */
    346     public int hashCode() {
    347     long bits = java.lang.Double.doubleToLongBits(getX());
    348     bits ^= java.lang.Double.doubleToLongBits(getY()) * 31;
    349     return (((int) bits) ^ ((int) (bits >> 32)));
    350     }
    

    来自:http://kickjava.com/src/java/awt/geom/Point2D.java.htm#ixzz1lMCZCCZw

    hashCode 实现简单指南见here

    【讨论】:

    • 警告所有使用此标识的人。哈希冲突是现实......例如pastebin.com/6wM3W3Wv
    【解决方案8】:

    来自 JDK 的 Point 类(继承自 Point2d):

    public int hashCode() {
        long bits = java.lang.Double.doubleToLongBits(getX());
        bits ^= java.lang.Double.doubleToLongBits(getY()) * 31;
        return (((int) bits) ^ ((int) (bits >> 32)));
    }
    

    这看起来比你的实现略好。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-20
      • 2023-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-16
      • 2010-10-19
      • 1970-01-01
      相关资源
      最近更新 更多