public class Test {
private final int m, n;
public Test(int m, int n) {
this.m = m;
this.n = n;
}
public int hashCode() { return n * m; }
public boolean equals(Object ob) {
if (ob.getClass() != Test.class) return false;
Test other = (Test)ob;
return m == other.m;
}
}
使用:
Set<Test> set = new HashSet<Test>();
set.put(new Test(3,4));
boolean b = set.contains(new Test(3, 10)); // false
int factor = 4;
int buckets = 1 << (factor-1) - 1; // 16
int mask = buckets - 1; // 15
int code = key.hashCode();
int dest = code & mask; // a number from 0 to 15 inclusive
Set<Foo> myFoos = new HashSet<Foo>();
Foo firstFoo = new Foo(123,"Alpha");
myFoos.add(firstFoo);
// later in the processing you get another Foo from somewhere
Foo someFoo = //use imagination here...;
// maybe you get it from a database... and it's equal to Foo(123,"Alpha)
if (myFoos.contains(someFoo)) {
// maybe you win a million bucks.
}
像hashset这样的容器依赖于hash函数来确定将它放在何处,以及在请求时从何处获取它。如果 A.equals(B) ,则哈希集希望 B . 如果你把 A 有价值的 V ,然后向上看 B ,你应该期望 V 回来(自从你说过 A.equals(B) ). 但是如果a.hashcode()!=b、 hashcode(),则hashset可能找不到放置它的位置。
6条答案
按热度按时间ct2axkht1#
当然:
使用:
从技术上讲,这应该是正确的,因为在这两种情况下m==3。
一般来说,hashmap的工作原理是这样的:它有一个通常称为“bucket”的可变数目。bucket的数量可以随着时间的推移而变化(随着条目的添加和删除),但它始终是2的幂。
假设一个给定的
HashMap
有16个桶。调用put()添加条目时,会计算键的hashcode(),然后根据存储桶的大小获取掩码。如果您(按位)将hashcode()与15(0x0f)相加,您将得到最后4位,等于一个介于0和15之间(含0和15)的数字:如果这个桶里已经有了一个条目,那就是所谓的碰撞。处理这个问题有多种方法,但是hashmap使用的方法(可能是最常见的方法)是bucketing。所有具有相同屏蔽哈希代码的条目都放入某种列表中。
因此,要确定给定的密钥是否已在Map中:
计算屏蔽哈希码;
找到合适的桶;
如果为空,则找不到密钥;
如果不是空的,则循环检查bucket中的所有条目checking equals()。
查看bucket是一个线性(o(n))操作,但它是在一个很小的子集上。hashcode bucket的确定基本上是常量(o(1))。如果bucket足够小,那么对hashmap的访问通常被描述为“接近o(1)”。
你可以对此做一些观察。
首先,如果有一堆对象都返回42作为散列码
HashMap
仍将有效,但它将作为一个昂贵的名单运作。访问将是o(n)(因为无论存储桶的数量如何,所有内容都将在同一个存储桶中)。我在面试中被问到过这个问题。第二,回到原点,如果两个物体相等(意思是a。
equals(b) == b.equals(a) == true
)但是有不同的散列码HashMap
将去寻找(可能)错误的桶导致不可预测和未定义的行为。qmb5sa222#
下面是一个小例子:
因此,假设为
firstFoo
是99999
它会在地球上的一个特定地点结束myFoos
哈希集。等你拿到票的时候someFoo
你就在房间里找myFoos
hashset,它需要生成相同的hashcode以便您可以找到它。lyfkaqu13#
这背后的想法是,如果两个对象的所有字段都具有相等的值,那么它们就是“相等”的。如果所有字段的值都相等,那么这两个对象应该具有相同的哈希值。
emeijp434#
这完全是因为哈希表。
由于哈希代码冲突的可能性,哈希表还需要检查标识,否则该表无法确定它是否找到了要查找的对象,或者找到了具有相同哈希代码的对象。所以每
get()
在哈希表中调用key.equals(potentialMatch)
在返回值之前。如果
equals()
以及hashCode()
如果你的行为不一致,你会有非常不一致的行为。比如说两个物体,a
以及b
,a.equals(b)
返回true,但是a.hashCode() != b.hashCode()
. 插入一个,hashset将为返回false.contains(b)
,但从该集合创建的列表将返回true(因为该列表不使用哈希代码)。很明显,那可能很糟糕。
odopli945#
这在第8项中进行了讨论:重写joshua bloch有效java的equals时始终重写hashcode:
bug的一个常见来源是未能重写hashcode方法。必须在每个重写equals的类中重写hashcode。否则将违反object.hashcode的一般约定,这将阻止类与所有基于哈希的集合(包括hashmap、hashset和hashtable)一起正常工作。
以下是从java.lang.object规范复制的合同:
在应用程序执行期间,每当在同一对象上多次调用hashcode方法时,只要不修改对象的equals比较中使用的信息,hashcode方法就必须始终返回相同的整数。从一个应用程序的一个执行到同一应用程序的另一个执行,这个整数不需要保持一致。
如果根据equals(object)方法两个对象相等,那么对两个对象中的每个对象调用hashcode方法必须产生相同的整数结果。
根据equals(object)方法,如果两个对象不相等,则对这两个对象中的每一个调用hashcode方法都必须产生不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。
当您无法重写hashcode时违反的关键规定是第二条:相等的对象必须具有相等的hashcode。根据类的equals方法,两个不同的示例在逻辑上可能相等,但是对于对象类的hashcode方法,它们只是两个没有太多共同点的对象。因此,object的hashcode方法返回两个看似随机的数字,而不是契约所要求的两个相等的数字。
例如,考虑以下简化的phonenumber类,其equals方法是根据第7项中的配方构造的:
假设您尝试将此类与hashmap一起使用:
在这一点上,你可能会想到
m.get(new PhoneNumber(408 , 867, 5309))
返回"Jenny"
,但它回来了null
. 请注意,涉及到两个phonenumber示例:一个用于插入hashmap,另一个equal示例用于(尝试)检索。phonenumber类未能重写hashcode导致两个相等的示例具有不相等的哈希代码,这违反了hashcode约定。因此,get方法在一个不同于put方法存储电话号码的哈希桶中查找电话号码。解决此问题非常简单,只需为phonenumber类提供适当的hashcode方法。[…]完整内容见第3章。
u3r8eeie6#
像hashset这样的容器依赖于hash函数来确定将它放在何处,以及在请求时从何处获取它。如果
A.equals(B)
,则哈希集希望B
. 如果你把A
有价值的V
,然后向上看B
,你应该期望V
回来(自从你说过A.equals(B)
). 但是如果a.hashcode()!=b、 hashcode(),则hashset可能找不到放置它的位置。