根据我的理解,我认为:

两个对象具有相同的hashcode是完全合法的。 如果两个对象相等(使用equals()方法),则它们具有相同的hashcode。 如果两个对象不相等,那么它们就不能有相同的hashcode

我说的对吗?

如果我没猜错的话,我有以下问题: HashMap在内部使用对象的hashcode。那么,如果两个对象可以具有相同的hashcode,那么HashMap如何跟踪它使用的键呢?

有人能解释一下HashMap内部是如何使用对象的hashcode的吗?


当前回答

HashMap是一个Entry对象数组。

把HashMap看作是一个对象数组。

看看这个对象是什么:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
… 
}

每个Entry对象表示一个键值对。如果一个桶有多个Entry,下一个字段引用另一个Entry对象。

有时候,两个不同对象的哈希码可能是相同的。在这种情况下,两个对象将保存在一个bucket中,并将显示为链表。 入口点是最近添加的对象。该对象引用具有下一个字段的另一个对象,以此类推。最后一项指向null。

当使用默认构造函数创建HashMap时

HashMap hashMap = new HashMap();

数组的大小为16,默认负载平衡为0.75。

添加新的键-值对

计算键的hashcode 计算元素应该放置的位置哈希% (arrayLength-1)(桶号) 如果你试图用一个已经保存在HashMap中的键添加一个值,那么值将被覆盖。 否则元素被添加到桶中。

如果存储桶已经有至少一个元素,则添加一个新元素并将其放置在存储桶的第一个位置。它的下一个字段指向旧元素。

删除

计算给定键的hashcode 计算桶号哈希% (arrayLength-1) 获取桶中第一个Entry对象的引用,并通过equals方法遍历给定桶中的所有条目。最终我们会找到正确的入口。 如果没有找到所需的元素,则返回null

其他回答

你的第三个断言是不正确的。

两个不相等的对象拥有相同的哈希码是完全合法的。它被HashMap用作“第一遍过滤器”,以便映射可以快速找到具有指定键的可能条目。然后测试具有相同哈希码的键是否与指定的键相等。

您不会希望要求两个不相等的对象不能具有相同的哈希码,否则将限制为232个可能的对象。(这也意味着不同类型甚至不能使用对象的字段来生成哈希码,因为其他类可以生成相同的哈希码。)

hashmap是这样工作的(这有点简化,但它说明了基本机制):

它有许多“桶”,用来存储键值对。每个桶都有一个唯一的编号——用来标识该桶。当您将一个键值对放入映射时,hashmap将查看键的哈希码,并将该对存储在标识符为键的哈希码的bucket中。例如:密钥的哈希码为235 ->,存储在桶号为235的桶中。(注意,一个桶可以存储多个键-值对)。

当您在hashmap中查找一个值时,通过给它一个键,它将首先查看您给出的键的哈希代码。然后,hashmap将查看相应的存储桶,然后它将通过equals()比较您给出的键与存储桶中所有对的键。

现在,您可以看到这对于在map中查找键-值对是多么高效:通过键的哈希代码,哈希映射立即知道要在哪个bucket中查找,因此它只需要测试该bucket中的内容。

看看上面的机制,你也可以看到对键的hashCode()和equals()方法有什么必要的要求:

If two keys are the same (equals() returns true when you compare them), their hashCode() method must return the same number. If keys violate this, then keys that are equal might be stored in different buckets, and the hashmap would not be able to find key-value pairs (because it's going to look in the same bucket). If two keys are different, then it doesn't matter if their hash codes are the same or not. They will be stored in the same bucket if their hash codes are the same, and in this case, the hashmap will use equals() to tell them apart.

HashMap是一个Entry对象数组。

把HashMap看作是一个对象数组。

看看这个对象是什么:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;
… 
}

每个Entry对象表示一个键值对。如果一个桶有多个Entry,下一个字段引用另一个Entry对象。

有时候,两个不同对象的哈希码可能是相同的。在这种情况下,两个对象将保存在一个bucket中,并将显示为链表。 入口点是最近添加的对象。该对象引用具有下一个字段的另一个对象,以此类推。最后一项指向null。

当使用默认构造函数创建HashMap时

HashMap hashMap = new HashMap();

数组的大小为16,默认负载平衡为0.75。

添加新的键-值对

计算键的hashcode 计算元素应该放置的位置哈希% (arrayLength-1)(桶号) 如果你试图用一个已经保存在HashMap中的键添加一个值,那么值将被覆盖。 否则元素被添加到桶中。

如果存储桶已经有至少一个元素,则添加一个新元素并将其放置在存储桶的第一个位置。它的下一个字段指向旧元素。

删除

计算给定键的hashcode 计算桶号哈希% (arrayLength-1) 获取桶中第一个Entry对象的引用,并通过equals方法遍历给定桶中的所有条目。最终我们会找到正确的入口。 如果没有找到所需的元素,则返回null

哈希映射的工作原理是哈希

HashMap get(Key k) method calls hashCode method on the key object and applies returned hashValue to its own static hash function to find a bucket location(backing array) where keys and values are stored in form of a nested class called Entry (Map.Entry) . So you have concluded that from the previous line that Both key and value is stored in the bucket as a form of Entry object . So thinking that Only value is stored in the bucket is not correct and will not give a good impression on the interviewer .

每当我们调用HashMap对象上的get(Key k)方法时。首先,它检查key是否为空。注意,HashMap中只能有一个空键。

如果key为null,则null键总是映射到哈希0,因此索引为0。

如果key不为空,那么它将在key对象上调用hashfunction,参见上述方法中的第4行,即key. hashcode(),因此在key. hashcode()返回hashValue之后,第4行如下所示

            int hash = hash(hashValue)

现在,它将返回的hashValue应用到自己的哈希函数中。

我们可能想知道为什么要再次使用hash(hashvalue)计算哈希值。答案是它可以防御低质量的哈希函数。

现在使用final hashvalue来查找存储Entry对象的bucket位置。条目对象像这样存储在桶中(哈希,键,值,bucketindex)

你在第三点上错了。两个条目可以具有相同的哈希码,但不相等。看一下HashMap的实现。从OpenJdk中获取。你可以看到它检查哈希值是否相等键值是否相等。如果第三点成立,那么检查键值是否相等就没有必要了。哈希码在键之前进行比较,因为前者是更有效的比较。

如果您有兴趣进一步了解这方面的知识,请参阅Wikipedia关于开放寻址冲突解决的文章,我认为这是OpenJdk实现使用的机制。这种机制与另一个答案中提到的“桶”方法略有不同。