Hashmap如何在Java工作?
#java #面试 #guide #jvm

4nt783qqrsetpzajgk9r.png

作为面试官,我喜欢问这个问题,因为它显示了候选人对数据结构,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?)。

更多信息