与面试官讨论Java Hashmaps的内部实现,以及如果我们覆盖equals()而不是Employee对象的HashCode()方法,它将如何表现。
他告诉我,对于默认的object.hashCode()实现,两个不同对象的hashCode永远不会相同,除非我们自己覆盖hashCode()。
另外,有人告诉我,一个存储桶中只能有一个唯一的Hashcode,并且具有相同哈希码的对象只能存储在一个存储桶中。我知道这与第一点相矛盾。杜!
从我记忆中,我告诉他Java Hashcode合同说两个不同的对象可能有相同的hashcode()。
根据我的采访者的说法,默认的object.hashcode()对于两个不同的对象永远不会有相同的hashcode(),这是真的吗?
甚至可以远程编写演示此代码的代码。根据我的理解,Object.hashcode()可以产生2 ^ 30个唯一值,如何产生碰撞,具有如此低的碰撞可能性,以证明两个不同的对象可以使用Object类方法获得相同的hashcode()。
或者他是对的,使用默认的Object.HashCode()实现,我们永远不会发生冲突,即两个不同的对象永远不会有相同的HashCode。如果是这样,为什么这么多java手册都没有明确说出来。
如何编写一些代码来演示这个?因为在演示这个时,我还可以证明hashmap中的一个桶可以包含不同的HashCodes(我试图向他展示hashMap被扩展的调试器,但他告诉我这只是逻辑实现而不是内部算法?)< / p>
答案 0 :(得分:10)
2 ^ 30个独特的价值听起来很多,但the birthday problem意味着我们不需要很多物品来发生碰撞。
以下程序在大约一秒钟内为我工作,并在对象196和121949之间发生冲突。我怀疑它将在很大程度上取决于您的系统配置,编译器版本等。
从Hashable
类的实现中可以看出,每个人都被保证是独一无二的,但仍然存在冲突。
class HashCollider
{
static class Hashable
{
private static int curr_id = 0;
public final int id;
Hashable()
{
id = curr_id++;
}
}
public static void main(String[] args)
{
final int NUM_OBJS = 200000; // birthday problem suggests
// this will be plenty
Hashable objs[] = new Hashable[NUM_OBJS];
for (int i = 0; i < NUM_OBJS; ++i) objs[i] = new Hashable();
for (int i = 0; i < NUM_OBJS; ++i)
{
for (int j = i + 1; j < NUM_OBJS; ++j)
{
if (objs[i].hashCode() == objs[j].hashCode())
{
System.out.println("Objects with IDs " + objs[i].id
+ " and " + objs[j].id + " collided.");
System.exit(0);
}
}
}
System.out.println("No collision");
}
}
答案 1 :(得分:3)
如果你有足够大的堆(假设64位地址空间)并且对象足够小(64位JVM上的最小对象大小是8字节),那么你将能够表示超过2 ^ 32个对象可以同时到达的。此时,对象的标识哈希码不能是唯一的。
但是,你不需要一个怪异的堆。如果你创建了一个足够大的对象池(例如在一个大型数组中)并随机删除并重新创建它们,那么(我认为)保证你会得到一个哈希码冲突......如果你继续做这么长时间。
旧版Java中哈希码的默认算法基于首次调用哈希码时对象的地址。如果垃圾收集器移动一个对象,并在第一个对象的原始地址创建另一个对象,并调用identityHashCode,则这两个对象将具有相同的标识哈希码。
当前(Java 8)默认算法使用PRNG。 “生日悖论”公式将告诉您一个对象的身份哈希码与另一个对象的身份哈希码相同的概率。
@BastianJ提到的-XXhashCode=n
选项具有以下行为:
hashCode == 0:返回一个新生成的伪随机数
hashCode == 1:使用随机变化的伪随机数对对象地址进行异或。
hashCode == 2:hashCode为1! (因此@ BastianJ的“作弊”答案。)
hashCode == 3:哈希码是一个递增的序列号。
hashCode == 4:对象地址的底部32位
hashCode&gt; = 5:这是Java 8的默认算法。它使用Marsaglia的xor-shift PRNG和特定于线程的种子。
如果您已下载OpenJDK Java 8源代码,您将在hotspot/src/share/vm/runtime/synchronizer.cp
中找到该实现。寻找get_next_hash()
方法。
这是证明它的另一种方式。给他看源代码!
答案 2 :(得分:2)
使用Oracle JVM并设置-XX:hashCode = 2。如果我记得正确,那么选择默认实现为&#34;常数1&#34;。只是为了证明你是对的。
答案 3 :(得分:2)
除了一些高尔夫球和统计数据之外,我几乎无法添加到Michael's answer(+1)。
关于迈克尔所链接的Birthday problem的维基百科文章,在给定特定大小的值空间的情况下,以所需概率获得碰撞所需事件数量的nice table。例如,Java的hashCode
有32位,值空间为40亿。要以50%的概率发生碰撞,大约需要77,000个事件。
以下是查找具有相同Object
的{{1}}的两个实例的简单方法:
hashCode
返回发生冲突所需的尝试次数。我跑了很多次并生成了一些统计数据:
static int findCollision() {
Map<Integer,Object> map = new HashMap<>();
Object n, o;
do {
n = new Object();
o = map.put(n.hashCode(), n);
} while (o == null);
assert n != o && n.hashCode() == o.hashCode();
return map.size() + 1;
}
这似乎与维基百科表中的数字一致。顺便说一下,我的笔记本电脑上只需要大约10秒钟,所以这远非一个病态的情况。
你是对的,但它需要重复:哈希码不是唯一的!