学习Java哈希表HashMap时可能会遇到的小困惑
原创
©著作权归作者所有:来自51CTO博客作者Mrrr_Li的原创作品,请联系作者获取转载授权,否则将追究法律责任
而当你使用HashMap存储键值对时,有可能会遇到一个疑问。
下面举一个我遇到的问题:
Person是一个实体类(已经重写了equals和hashCode方法) ,这里的HashMap是官方的HashMap
= new com.mj.set.Person(12,1.67f,"jack");
Person p2 = new com.mj.set.Person(12,1.67f,"jack");
java.util.Map<Object, Integer> map2 = new java.util.HashMap<>();
map2.put(p1,1);
map2.put(p2,2);
System.out.println(map2.size());
System.out.println(map2.containsKey(p1));
当你在主函数执行这段代码时,会发现结果为1,true。
而你可能会有这样的疑惑:你不是已经用p2覆盖了p1吗,为什么map2中还是包含p1?
作出解释:
你翻阅Java官方的HashMap你会发现,那个containsKey(K key)方法是通过传入hash(key);,以及key查找node是否为空,如果为空,则返回false, 否则返回true。
getNode方法
/**
* Implements Map.get and related methods.
*
* @param hash hash for key
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
/**
* Returns {@code true} if this map contains a mapping for the
* specified key.
*
* @param key The key whose presence in this map is to be tested
* @return {@code true} if this map contains a mapping for the specified
* key.
*/
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
而你细看上面创建的两个对象你会发现,两个对象的成员变量的内容都是一致的
Person类
package com.mj.set;
public class Person implements Comparable<Person> {
private int age; // 10 20
private float height; // 1.55 1.67
private String name; // "jack" "rose"
public Person(int age, float height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
@Override
/**
* 用来比较2个对象是否相等
*/
public boolean equals(Object obj) {
// 内存地址
if (this == obj) return true;
if (obj == null || obj.getClass() != getClass()) return false;
// if (obj == null || !(obj instanceof Person)) return false;
// 比较成员变量
Person person = (Person) obj;
return person.age == age
&& person.height == height
&& (person.name == null ? name == null : person.name.equals(name));
}
@Override
public int hashCode() {
int hashCode = Integer.hashCode(age);
hashCode = hashCode * 31 + Float.hashCode(height);
hashCode = hashCode * 31 + (name != null ? name.hashCode() : 0);
return hashCode;
}
@Override
public int compareTo(Person o) {
return age - o.age;
}
}
既然内容都一致,这就意味着hashCode的返回值都一致,则hash函数的结果也一致:
Java官方的hash函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
而当你查看官方的getNode方法时,你会发现有这么几句代码
即当hash()的返回值相同以及key.equals(k)都为true时,才返回找到的对应的节点,而之前已经说过p1与p2的hash值是一定相等的,
再看equals方法
/**
* 用来比较2个对象是否相等
*/
public boolean equals(Object obj) {
// 内存地址
if (this == obj) return true;
if (obj == null || obj.getClass() != getClass()) return false;
// if (obj == null || !(obj instanceof Person)) return false;
// 比较成员变量
Person person = (Person) obj;
return person.age == age
&& person.height == height
&& (person.name == null ? name == null : person.name.equals(name));
}
由于p1和p2成员变量的内容一致,则euqals方法一定返回true
而你查找p1时,通过p1创建的节点已经存储在了map2中,那么一定会找到这个node结点,即getNode方法会返回找到的节点。则containsKey(p1)必然返回true
另外,HashMap的遍历结果是无序的。