在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;
}
}
我使用类Point
的对象作为HashMap
中的键和HashSet
中的元素。
什么是hashCode
功能的最佳候选人?我会把它变成两倍,这样左边的部分是x而右边的部分是y,例如:x = 4, y = 12
,然后hashCode
返回4.12
。但是通过实现,它不能是double,只能是int。
这不是一个选择:
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));
}
因为值x
和y
可能太长,所以它们在一起它们将不会被转换。
你不能改变hashCode
的类型,也不应该改变。
我会选择以下内容:
public int hashCode() {
return x * 31 + y;
}
注意,这意味着对于大多数情况((例如,添加或异或),(a,b)与(b,a)不同。如果您经常在现实生活中使用“切换”值的键,这将非常有用。
它不是唯一的 - 但哈希码不一定是。对于相等的值(为了正确性),它们必须是相同的,并且(对于效率)对于非等值而言“通常”是不同的,具有合理的分布。
一般来说,我通常遵循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;
}
其中field1Hash
将是引用类型字段的哈希码(或0表示空引用),int
本身用于int值,某种类型的哈希从64位到32用于long
等。
编辑:我不记得为什么31和17一起工作的细节。它们都是素数的事实可能是有用的 - 但是从我记忆中来看,为什么这样的哈希背后的数学通常是合理的(尽管不如预先知道可能值的分布的哈希那样)要么困难,要么不太了解。我知道乘以31便宜(左移5并减去原值)......
只需使用java.util.Objects.hash(Object... values)。
public int hashCode() {
return Objects.hash(field1,field2);
}
Objects.hash实际上调用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;
}
我知道不相等的对象可以使用相同的哈希码。但是,冲突越多,性能就越差(例如,在哈希表中)。
据我所知,Z²→Z的最佳映射是“优雅的配对功能”(google it)。这是实施
// 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,那么这将具有零散列冲突。
它经常值得考虑Apache Commons HashCodeBuilder
这个类可以为任何类构建一个好的hashCode方法。它遵循Joshua Bloch撰写的Effective Java一书中规定的规则。编写好的hashCode方法实际上非常困难。本课程旨在简化流程
我肯定会建议查看引用的书Effective Java。
存在生成哈希码操作的共同策略。在你的情况下,这将是:
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
result = prime * result + y;
return result;
}
你可能想看看Google Guava's Objects.hashCode(Object...)方法。
public int hashCode() {
return Objects.hashCode(x, y);
}
这个问题已经很老了,但我认为只要java存在,这个想法就是实际的。让我们分析上面的方法:
Objects.hashCode(...)
能够流畅而清晰地表达需要做什么,但它使用varargs(隐式创建一个数组),而且,它隐含地将每一个原语打包,传递给方法。x * 31 + y
性能高效:没有装箱,没有使用显式或隐式数组创建操作。但是,目前还不清楚需要做些什么。为什么31,不是42?对于那些熟悉散列是如何工作的人来说,理解这些代码并不困难,但对其他人来说又是什么呢?第二个缺陷是难以扩展:例如,如果您想要转换3D并添加z坐标,则很容易忘记在散列代码中添加新值,因为它会强制您复制粘贴几乎相同的代码倍。我可以介绍第三种方法,上面的答案没有提到:
@Override
public final int hashCode()
{
final int[] numbers = {x, y};
return Arrays.hashCode(numbers);
}
它使用临时数组来保存被散列的整数,并调用自Java 1.5以来可用的Arrays.hashCode(),还有其他原始类型的版本。
优点:这是DRY,流利,完全清楚需要做什么。它不会受到隐式装箱的影响,也不会使用隐式的vararg。它相对快速和便宜。通过在数组初始值设定项中添加额外的数字可以轻松扩展它。
缺点:它没有复制粘贴方法那么快。如果经常调用哈希码,请考虑它。
最好的祝福。
尝试添加他们的哈希码。 ?
return new Integer(x).hashCode()+ new Integer(y).hashCode();