只有两个 int 属性的自定义类的哈希码是什么?

2022-09-01 03:32:31

在Java中,我有一个类,它表示一个带有int坐标的点。

public class Point {
    int x = -1;
    int y = -1;

    public Point (int xNew, int yNew) {
        x = xNew; y = yNew;
    }

    public boolean equals (Object o) {
        // no need for (o instanceof Point) by design
        return x == ((Point)o).x && y == ((Point)o).y;
    }
}

我将类的对象用作 a 中的键和 .PointHashMapHashSet

该功能的最佳候选者是什么?我会把它做成双倍,这样左边的部分是x,右边的部分是y,例如:,然后返回。但是通过实现,它不能是双精度的,只能是int。hashCodex = 4, y = 12hashCode4.12

这不是一个选项:

public int hashCode() {
    // no need to check for exception parseInt since x and y are valid by design
    return Integer.parseInt(Integer.toString(x) + Integer.toString(y));
}

因为值和可能太长,所以它们在一起不会被转换。xy


答案 1

您无法更改 的类型,也不应更改 。hashCode

我会选择这样的东西:

public int hashCode() {
    return x * 31 + y;
}

请注意,这意味着在大多数情况下,(a,b)与(b,a)不同(与例如添加或XOR-ing不同)。如果您经常在现实生活中最终获得“切换”值的键,这可能很有用。

它不是唯一的 - 但哈希代码不一定是。对于相等的值(对于正确性),它们必须相同,并且(对于效率)对于不相等的值,它们“通常”不同,并且具有合理的分布。

一般来说,我通常遵循Josh Bloch在 Effective Java 中建议的相同模式:

public int hashCode() {
    int hash = 17;
    hash = hash * 31 + field1Hash;
    hash = hash * 31 + field2Hash;
    hash = hash * 31 + field3Hash;
    hash = hash * 31 + field4Hash;
    ...
    return hash;
}

其中,引用类型字段的哈希代码(或 null 引用的 0)、int 值本身、从 64 位到 32 位的某种哈希等。field1Hashintlong

编辑:我不记得为什么31和17一起工作的细节。它们都是素数的事实可能是有用的 - 但据我所知,为什么像这样的哈希通常是合理的(尽管不如哈希值的分布事先知道)背后的数学要么很难理解,要么不太清楚。我知道乘以31很便宜(向左移5并减去原始值)...


答案 2

我知道不相等的对象具有相同的哈希码是可以的。但是,冲突越多,性能就越差(例如,在哈希表中)。

据我所知,从Z²→Z的最佳映射是“优雅的配对功能”(谷歌它)。这是实现

// x,y must be non-negative
int elegant(int x, int y) {
    return x < y ? y * y + x : x * x + x + y;
}


// returns a unique number for every x,y pair
int elegantSigned(int x, int y) {
    if (x < 0) {
        if (y < 0)
            return 3 + 4 * elegant(-x - 1, -y - 1);
        return 2 + 4 * elegant(-x - 1, y);
    }
    if (y < 0)
        return 1 + 4 * elegant(x, -y - 1);
    return 4 * elegant(x, y);
}

一旦你得到乘法溢出,这将开始重叠。如果 x 和 y 的绝对值小于约 46000,则哈希冲突为


推荐