作为一个入门级的程序员 , HashMap 我们肯定都用过,但是我们是否真正了解过它呢? 那么看了这篇文章,看看是否让你对HashMap有新的认识。
首先HashMap基本的用法大家肯定都熟悉,像下面这样:
Map testMap1=new HashMap<String,String>();
testMap1.put("Mrwang", "我是一个垃圾");
testMap1.put("Mrliu", "我是一个菜鸟");
那它内部是怎么存储的呢?下面我们来探究其背后的原理:
简单来说,HashMap是数组和链表的结合(这里先不考虑java8的优化), ArrayList 和 LinkList大家都熟悉,他们就是数组和链表的代表,HashMap 结合了两者。它的数据结构如下,水平方向是一个数组,垂直方向是个链表。
插入操作:
首先获取对象的hashCode, 通过%数组长度得到对象所要插入的数组下标(这个过程也叫散列),然后在垂直方向上遍历链表,如果有重复对象,就将其替换,如果没有,就向下移动,最后插入队列尾部。
获取操作:
查找方法同插入相同,首先计算hashCode, 然后通过模运算找到数组所在下标,然后遍历链表查找。
两个步骤都说到了一个比较两个对象是否相等,那么hashMap中如何判断两个对象是一个对象,并将其替换的呢?
不着急,我们先来看一个例子:
public static void main(String[] args) {
// TODO Auto-generated method stub
Map testMap=new HashMap<Object,String>();
Person p1=new Person();
p1.setName("a");
p1.setAge(22);
p1.setId("id1");
Person p2=new Person();
p2.setName("a");
p2.setAge(22);
p2.setId("id1");
testMap.put(p1, "我是第一个人");
testMap.put(p2, "我是第二个人");
Iterator iterator=testMap.entrySet().iterator();
while(iterator.hasNext()) {
Map.Entry entry=(Map.Entry)iterator.next();
Person p=(Person)entry.getKey();
String value=(String)entry.getValue();
System.out.println("person:"+p.toString()+",value:"+value);
}
}
我们插入两个对象到HashMap中 ,注意这里的key不是基础类型,而是我们自定义类型Person, 看一下它的输出:
person:name->a,age->22,id->id1,value:我是第一个人
person:name->a,age->22,id->id1,value:我是第二个人
两个对象的值都一样,但是都能put成功,说明HashMap没有将其认为是一个对象,这是为什么呢?
这里先来了解一下HashMap 判断Key是否存在的规则:
1.首先计算key的hashCode, 如果hashCode在hashMap中不存在,直接插入。
2.如果hashCode 已存在,那么找出所有hashCode相同的key,分别调用key的equals方法判断当前的key是否与其相同。如果equals()返回true,则说明需要添加的key已经存在,那么hashMap会用新的value替换掉旧的value的值; 如果equals()都返回false,则说明没有相同的key存在,则在hashMap中建立新的映射关系。
所以我们找到原因了,肯定是两个Person对象在HashMap中比较中要么就是HashCode() 比较不同,要么就是equals()比较返回false。我们依次来看。
我们没有重写Person的hashCode和equals方法,所以所调用的都是它的父类的,也就是Object的hashCode和equals方法,下面依次来看:
先看Object的hashCode:
public native int hashCode();
是个native方法,这里就不追了,注释上有这么一段话:
* As much as is reasonably practical, the hashCode method defined by
* class {@code Object} does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the
* Java™ programming language.)
我们可以理解为是hashCode() 返回的是对象的内存地址转化为的整数。
上例中p1和p2是两个对象,内存地址不同,hashCode() 不相同。
再看Object的equals():
public boolean equals(Object obj) {
return (this == obj);
}
好像也是比较的是内存地址,Object对象只有当两个对象是同一对象的时候,才会返回true,否则返回false.
结合上例,在hashMap的比较中equals() 也不会比较通过(当然hashCode不同不会走到这一步)
回到上例,我们如果想让属性相同的Person对象在HashMap中只保存一个,怎么来实现呢,很简单,就是重写hashCode和equals:
package com.example.hashmaptest;
public class Person {
public static final Object HashObject=new Object();
String name;
int age;
String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "name->"+name+",age->"+age+",id->"+id;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(obj instanceof Person) {
Person p=(Person)obj;
if(p.age==this.age
&&p.id==this.id
&&p.name==this.name) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return HashObject.hashCode();
}
}
如果理解了上面的原理,这个类的equals和hashCode方法的重写应该很简单了吧。如果以后遇到人问到你hashMap的问题,是不是不会迷茫了呢。