HashMap和HashTable都采用了hash法进行索引,他们有以下区别:
- HashMap是HashTable的轻量级实现,是非线程安全的,它是根据键的hash值存取数据,具有很快的访问速度。HashMap和HashTable都实现了Map接口。但是HashMap的key和value可以为空,HashTable不允许key,value为空。如果HashTable的key或value为空,则抛出空指针异常。
- HashMap中没有contains()方法。HashMap和HashTable中有containsKey()和containsValue()方法。HashTable有contains()方法,这个contains()方法指的是包含value。
- HashTable是线程安全的,HashMap不是线程安全的。多个线程访问HashMap时候需要对HashMap同步。而HashTable不需要。
- HashMap和HashTable采用的hash/rehash算法都几乎一样,性能不会有很大差异。
- HashMap中hash数组默认大小是16。HashTable中hash数组默认大小是11,增加方式是old*2+1
- Hash值得使用不同,HashTable直接使用对象的hashCode
HashMap中添加元素和查找元素的原理:
HashMap中添加键值对<key,value>的过程如下:
首先调用key的hashCode()方法生成一个hash值h1:
- 如果这个hash值h1在HashMap中不存在,则直接将这个<key,value>值添加进HashMap
- 如果这个hash值h1存在,那么找出hash值为h1的所有的key,调用key的equals()方法,判断当前的key和已经存在的key是否相同,如果相同equals()返回true,说明有相同的key的键值对存在,那么直接覆盖key对应的value;如果不同equals()方法返回false,说明要添加的键值对在HashMap中不存在,那么,那么添加这个键值对进去。当新增加的key的hash值已经在HashMap中存在就会产生冲突,处理冲突方式有开放地址法,再hash法,链地址法等。
HashMap中通过key查找value过程:
首先调用key的hashCode()方法,获取到key对应的hash值h1,这样就可以确定键为key的所有的值存储的首地址,如果h1对应的key值有多个,那么程序会遍历所有的key,通过调用key的equals()方法来判断key的内容是否相等。相等则返回key对应的value,没找到返回null;
看看下面的例子:
例子1:
package com.experiment.xjtuse;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Test1 {
public static void main(String[] args) {
HashMap<String, String> map=new HashMap<String, String>();
map.put("a", "bbb");
map.put("b", "ggg");
map.put("a", "ccc");
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry=(Map.Entry)it.next();
String key=(String)entry.getKey();
String value=(String)entry.getValue();
System.out.println("key:"+key+" value:"+value);
}
}
}
输出结果为:
key:b value:ggg
key:a value:ccc
例子2:
package com.experiment.xjtuse;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
class Person{
String id;
String name;
public Person(String id,String name){
this.id=id;
this.name=name;
}
public String toString(){
return "id:"+this.id+" name:"+this.name;
}
}
public class Test2 {
public static void main(String[] args) {
HashMap<Person, String> map=new HashMap<Person, String>();
map.put(new Person("111", "彭召"), "666");
map.put(new Person("111", "彭召"), "666");
map.put(new Person("222", "小丁"), "222");
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry=(Map.Entry)it.next();
Person p1=(Person) entry.getKey();
String s1=(String)entry.getValue();
System.out.println(p1.toString()+" "+s1);
}
}
}
输出结果为:
id:111 name:彭召 666
id:111 name:彭召 666
id:222 name:小丁 222
例子3:
package com.experiment.xjtuse;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
class Person0{
String id;
String name;
public Person0(String id,String name){
this.id=id;
this.name=name;
}
public String toString(){
return "id:"+this.id+" name:"+this.name;
}
public int hashCode(){
return id.hashCode();
}
public boolean equals(Object o){
Person0 p=(Person0)o;
return p.id.equals(this.id);
}
}
public class Test3 {
public static void main(String[] args) {
HashMap<Person0, String> map=new HashMap<Person0, String>();
map.put(new Person0("111", "彭召"), "666");
map.put(new Person0("111", "彭召"), "666");
map.put(new Person0("222", "小丁"), "222");
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry=(Map.Entry)it.next();
Person0 p1=(Person0) entry.getKey();
String s1=(String)entry.getValue();
System.out.println(p1.toString()+" "+s1);
}
}
}
输出结果为:
id:222 name:小丁 222
id:111 name:彭召 666
这里例子1中String类中重写了hashCode()和equals()方法,而例子2中没有重写这两个方法,默认调用继承的Object类的这两个方法。而Object的hashCode()方法返回的是对象在内存中的首地址,由于两个person对象在内存的堆中的不同位置保存,因此首地址不同,返回的hashcode不同,而它的equals()方法是看着两个对象是不是同一个对象,是的话返回true,否则返回false。因此两个对象hashcode不同会被插入到HashMap不同的地方。如果想根据id相同就认为是同一个person则要重写这两个方法。就像例子3那样。
一般来说,重写了equals()方法就必须重写hashCode方法,如果两个对象相等,那么他们一定有相同的hashCode,但是如果两个对象的hashCode相等,他们不一定是相等的对象。