根据我的理解:
- 两个对象拥有相同的哈希码是完全合法的。
- 如果两个对象使用equals()方法得出相等的结果,则它们具有相同的哈希码。
- 如果两个对象不相等,则它们不能拥有相同的哈希码。
我的理解正确吗?
如果我的理解正确,那么我有以下问题:
HashMap
在内部使用对象的哈希码。那么,如果两个对象可以拥有相同的哈希码,那么HashMap
如何跟踪它所使用的键呢?
有人能解释一下HashMap
在内部如何使用对象的哈希码吗?
根据我的理解:
我的理解正确吗?
如果我的理解正确,那么我有以下问题:
HashMap
在内部使用对象的哈希码。那么,如果两个对象可以拥有相同的哈希码,那么HashMap
如何跟踪它所使用的键呢?
有人能解释一下HashMap
在内部如何使用对象的哈希码吗?
hashCode()
方法返回不同的哈希码,那么键类的equals()
和hashCode()
方法将违反协议,并且在使用这些键在HashMap
中时会得到奇怪的结果。 - JesperHashMap
的源代码,它可以在JDK安装目录下的src.zip
文件中找到。 - Jesper你的第三个断言是错误的。
两个不相等的对象具有相同的哈希码是完全合法的。这被HashMap
用作“第一次筛选”,以便该映射可以快速找到具有指定键的可能条目。然后,使用相同哈希码的键将与指定键进行相等性测试。
如果要求两个不相等的对象不能具有相同的哈希码,那么这将限制您只能拥有232个可能的对象。 (这也意味着不同类型甚至无法使用对象的字段生成哈希码,因为其他类可以生成相同的哈希码。)
HashMap
是一个 Entry
对象数组。
可以把 HashMap
看作是一个对象数组。
看看这个 Object
是什么:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
…
}
每个 Entry
对象表示一个键值对。如果一个 bucket 中有多个 Entry
,则字段 next
将指向另一个 Entry
对象。next
字段引用另一个对象,以此类推。最后一个条目引用 null
。HashMap
时,HashMap hashMap = new HashMap();
该数组的大小为16,负载均衡默认为0.75。
hash % (arrayLength-1)
HashMap
中的键的值,则该值会被覆盖。如果桶已经有至少一个元素,新元素将被添加并放置在桶的第一位置。它的 next
字段指向旧元素。
hash % (arrayLength-1)
Entry
对象的引用,并通过equals方法迭代遍历给定桶中的所有条目。最终我们将找到正确的Entry
。
如果没有找到所需的元素,则返回null
hash % (arrayLength-1)
,正确的应该是 hash % arrayLength
。但实际上在 Java 中,它是 hash & (arrayLength-1)
。这是因为 Java 使用二的幂次方(2^n
)作为数组长度,取最低的 n
位来计算哈希值。 - westonint
类型的变量,当然可以是负数,在负数上进行模运算将会得到一个负数。 - Eugene以下是对HashMap
机制的简要描述,适用于Java 8
版本,(与Java 6可能略有不同)
hash()
来计算哈希值,并决定对于给定的键使用哈希表的哪个桶。Map.Entry
HashMap.Node
节点的链表版本。
它可以表示:
HashMap.TreeNode
Node[] table
Set<Map.Entry> entrySet
实体的集合。int size
float loadFactor
int threshold
threshold = capacity * loadFactor
int hash(key)
如何将哈希映射到桶中?
使用以下逻辑:
static int hashToBucket(int tableSize, int hash) {
return (tableSize - 1) & hash;
}
在哈希表中,容量指的是桶的数量,可以通过table.length
获取。
也可以通过threshold
和loadFactor
计算得到,因此不需要定义为一个类字段。
可以通过capacity()
获取有效容量。
threshold
时,会将哈希表的容量(table.length
)加倍,然后对所有元素执行重新哈希以重建表。O(1)
,因为:
O(1)
。O(1)
。O(1)
而不是O(log N)
。哈希码(hashcode)决定了哈希表中要检查哪个桶。如果一个桶里有多个对象,那么就会执行线性查找来找到与所需对象相等的项(使用 equals()
方法)。
换句话说,如果你拥有完美的哈希码,则哈希表访问是恒定的,你永远不必遍历桶(严格来说,你还需要有 MAX_INT 个桶,Java 实现可能会在同一个桶中共享一些哈希码以减少空间需求)。如果您的哈希码最差(始终返回相同的数字),则哈希表访问变为线性,因为您必须搜索映射中的每个项目(它们都位于同一个桶中)才能获得所需内容。
大部分情况下,好的哈希码并不完美,但足够唯一,可以给您提供或多或少恒定的访问。
import java.util.HashMap;
public class Students {
String name;
int age;
Students(String name, int age ){
this.name = name;
this.age=age;
}
@Override
public int hashCode() {
System.out.println("__hash__");
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
System.out.println("__eq__");
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Students other = (Students) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public static void main(String[] args) {
Students S1 = new Students("taj",22);
Students S2 = new Students("taj",21);
System.out.println(S1.hashCode());
System.out.println(S2.hashCode());
HashMap<Students,String > HM = new HashMap<Students,String > ();
HM.put(S1, "tajinder");
HM.put(S2, "tajinder");
System.out.println(HM.size());
}
}
Output:
__ hash __
116232
__ hash __
116201
__ hash __
__ hash __
2
因此,我们可以看出,如果对象S1和S2都具有不同的内容,则我们可以确信我们覆盖的Hashcode方法将为这两个对象生成不同的哈希码(116232,11601)。现在,由于存在不同的哈希码,所以它甚至不会打扰调用EQUALS方法。因为不同的哈希码保证了对象之间的内容不同。
public static void main(String[] args) {
Students S1 = new Students("taj",21);
Students S2 = new Students("taj",21);
System.out.println(S1.hashCode());
System.out.println(S2.hashCode());
HashMap<Students,String > HM = new HashMap<Students,String > ();
HM.put(S1, "tajinder");
HM.put(S2, "tajinder");
System.out.println(HM.size());
}
}
Now lets change out main method a little bit. Output after this change is
__ hash __
116201
__ hash __
116201
__ hash __
__ hash __
__ eq __
1
We can clearly see that equal method is called. Here is print statement __eq__, since we have same hashcode, then content of objects MAY or MAY not be similar. So program internally calls Equal method to verify this.
Conclusion
If hashcode is different , equal method will not get called.
if hashcode is same, equal method will get called.
Thanks , hope it helps.
两个对象相等,意味着它们具有相同的哈希码,但反之不一定成立。
2个相等的对象------>它们具有相同的哈希码
2个拥有相同哈希码的对象----xxxxx-->它们不相等
Java 8中HashMap的更新-
你可以在你的代码中执行以下操作-
myHashmap.put("old","old-value");
myHashMap.put("very-old","very-old-value");
假设您的哈希码对于两个键 "old"
和 "very-old"
返回相同。那么会发生什么。
myHashMap
是一个 HashMap,假设最初您没有指定其容量。因此,默认容量为 16。因此,当您使用 new 关键字初始化 hashmap 时,它创建了 16 个桶。现在当您执行第一条语句时-
myHashmap.put("old","old-value");
然后计算"old"
的哈希码,由于哈希码可能是非常大的整数,所以Java内部执行了以下操作 - (这里的hash是哈希码,>>>是右移)
hash XOR hash >>> 16
class Entry{
final Key k;
value v;
final int hash;
Entry next;
}
现在,当您执行下一个语句时 -
myHashmap.put("very-old","very-old-value");
"very-old"
和"old"
的哈希值相同,因此这个新的键值对再次被发送到相同的索引或桶中。但由于此桶不为空,因此Entry对象的next
变量用于存储此新的键值对。
对于每个具有相同哈希码的对象,它们都将被存储为链接列表,但指定了TRIEFY_THRESHOLD的值为6。因此在达到此值后,将把链接列表转换为平衡树(红黑树),其中第一个元素作为根节点。