作为面试官,我喜欢问这个问题,因为它显示了候选人对数据结构,JVM内部的理解程度,以及一般而言扔入未知领域时的思维方式。
那么,为什么我要与您分享此信息?我看到许多候选人无法正确回答这个问题(大三,中级和老年人)。由于我已经问了很长时间,所以我相信我应该替换它并与世界分享答案。
在本文中,我会通过对每个步骤进行详细说明来介绍哈希图问题。
让我们开始!
equals()和hashCode()函数之间Java中的合同是什么?
众所周知,在Java中,所有对象都直接或间接地从包括equals()和HashCode()函数的对象类继承。
如果您会读取equals()的文档,您可能会注意到以下内容:
请注意,通常有必要在此方法被覆盖时覆盖
。hashCode
方法,以维护hashCode
方法的一般合同,该方法指出,相等的对象必须具有相等的哈希码。
但这是什么意思?
这意味着,如果我们有两个相等的对象,则必须具有相同的哈希代码。但是,如果对象不相等,则不能保证它们具有不同的哈希码。
将元素获取hashmap的复杂性是什么?
正如任何一年级的计算机科学学生都会告诉您的,从hashmap中获得元素的元素具有恒定的O(1)
。
那很容易!还是这样?
如果被覆盖以返回常数值,将会发生什么?
假设作为开发人员,我创建了以下类:
class MyClass {
@Override
public boolean equals(Object other) {
if(!(other instanceof MyClass)) return false;
return this == other;
}
@Override
public in hashCode() {
return 1;
}
}
现在,在我的代码中,我正在创建一个百万个对象并将它们存储在哈希地图中:
Map<MyClass, Integer> map = new HashMap<>();
IntStream.range(0, 1_000_000)
.forEach(i -> map.put(new MyClass(), i));
新的哈希代码功能将如何影响我们的性能?
要回答这个问题,我们必须首先挖掘hashmap在Java中的工作方式。
只要您想在地图中添加新元素,就会计算此密钥的哈希代码,并根据其值,选择一个包含该值的桶(在这种情况下为我们的对象)。如果发生碰撞(具有相同哈希代码的2个或多个对象),使用某种数据结构(例如链接列表)将键和值存储为一对
现在让我们假设我们要从地图中获取一个元素,以下操作将发生:
-
将计算密钥的哈希码
-
使用equals()函数在链接列表上迭代hashmap,直到找到正确的键和值对
-
找到正确的键,如果找不到键,则将返回该值或无效。
请注意,通常的假设是哈希函数均匀分布,这意味着我们期望每个存储桶中有很少的元素,因此可以将对正确键的搜索视为恒定时间。
但是,在我们的示例中,所有密钥都将具有相同的哈希代码,因此最终放在同一存储桶中。因此,从地图中获取值将需要(在最坏的情况下)扫描整个链接列表,并需要O(n)
的线性时间。
我们可以比线性时间做得更好吗?
我们现在的问题是,存储桶使用链接列表,即未排序的数据结构。如果我们使用自然界排序的数据结构怎么办?
如果我们用平衡的二进制树替换了桶实现,我们可以确保可以使用O(log(n))
的最差复杂性找到桶中的每个元素,这比我们以前的要好得多。
实际上,自Java 8以来,Hashmap的内部实现完全改变了。
奖金:如何使用hashmap实现标签?
让我们从问题开始,什么是哈希集?
a主题集是一个集合(无序的元素集合),其中相同的哈希代码仅存在一次。
我们可以通过将我们的值作为映射作为地图的键来轻松地实现标签,并将其作为值。实际上,这再次完全是实现Java选择的。
结论
您可以看到,问题涵盖了许多领域:
-
对数据结构的一般理解
-
对Java内部的理解
-
检查候选人的想法,以防他们不记得Java的内部(要明确,没有人期望Wikipedia回答这个问题)
此外,可以扩展问题以涵盖更多主题,例如并发(您将如何实现分布式hashmap?)。