HashMap实际上是一个散列表结构的数据结构,每个元素存放链表头结点的数组,即数组和链表的结合体。
jdk1.8之前,是数组和链表实现
jdk1.8之后,采用数组加链表加红黑树实现,当链表的长度超过阈值8时,链表转为红黑树。
HashMap是基于哈希表的Map接口实现(Map是存储键值对的容器,底层基于哈希表实现,对于Map的键可以是任意类型的对象,但不能够有重复的键,每个键对应一个值)
哈希冲突:两个不同的元素,通过哈希函数得出相同的存储地址,即对某个元素进行哈希运算得到的存储地址被其他元素占用。
解决方法:开放地址法:继续寻找下一块存储地址
链地址法:采用数组加链表(HashMap采用链地址法)
HashMap继承关系
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>,Cloneable,Serializable
AbstractMap<K,V>是Map接口的抽象类,可以实现Map接口的大部分方法
Cloneable接口,表示可以进行拷贝
Serializable接口表示可以将对象保存在本地。
hash运算:
public int hash(K key){
int h;
return (h = key.hashCode()) ^(h>>>16);
}
目的是为即将存入HashMap的元素的key计算hash值,将此hash值作为索引,放入哈希中
put方法:
public void put(K key,V value){
int hash = hash(key);
int index = (table.length-1) & hash;
//判断是否存在节点
Node<K,V> firstNode = table[index];
//不存在 new Node 直接放在该位置
if(firstNode == null){
table[index] = new Node(hash,key,value);
size++;
}else {
//存在 遍历链表
//key已经存在于链表中,覆盖value
if(firstNode.key.equals(key)){
firstNode.value = value;
}else {
//遍历链表
Node<K,V> tmpNode = firstNode;
while (tmpNode.next !=null || !tmpNode.key.equals(key)){
tmpNode = tmpNode.next;
}
if(tmpNode.next == null){
if(tmpNode.key.equals(key)){
tmpNode.value = value;
}else {
//key不存在于链表中
tmpNode.next= new Node(hash,key,value);
size++;
}
}else {
tmpNode.value = value;
}
}
}
}
put方法是存储方法,如果HashMap中不存在节点,直接插入,如果存在,遍历链表,若链表中存在相应的key值,直接覆盖掉原先的value,若不存在,在链表末尾插入。
get方法:
public V get(K key){
int hash = hash(key);
int index = (table.length-1) & hash;
Node<K,V> firstNode = table[index];
if(firstNode == null){
return null;
}
if(hash == firstNode.hash && key == firstNode.key){
return firstNode.value;
}else {
//遍历链表
Node<K,V> tmpNode = firstNode.next;
while (tmpNode != null){
if(tmpNode.hash == hash && tmpNode.key == key){
return tmpNode.value;
}
tmpNode = tmpNode.next;
}
}
return null;
}
查询方法:如果链表为空,返回null,如果头结点的hash和key与查询值相同,返回value值,否则,遍历链表,寻找相同的hash和key,找到返回value,否则返回null。
在get方法中,需要比较hash的原因:
if(hash == firstNode.hash && key == firstNode.key){
return firstNode.value;
1、哈希表是一个有限集合,固定长度为32位,对象和hashCode的关系并不是一对一的,所以hash相等但也可能对象不同。
2、判断元素是否相等时先用hashCode判断,如果hashCode不同,则对象不同,如果HashCode相同,对象再进行比较,效率更高。
remove方法:
public boolean remove(K key){
int hash = hash(key);
int index = (table.length-1) & hash;
Node<K,V> firstNode = table[index];
if(firstNode == null){
return false;
}else {
if(firstNode.next == null){
if(firstNode.hash == hash && firstNode.key.equals(key)){
table[index] = null;
return true;
}
}else {
if(firstNode.hash == hash && firstNode.key.equals(key)){
table[index] = firstNode.next;
return true;
}else {
//链表存在于多个节点则需要遍历链表进行比较
while (firstNode.next != null ){
if(firstNode.next.hash == hash && firstNode.next.key.equals(key)){
firstNode.next = firstNode.next.next;
return true;
}else {
firstNode = firstNode.next;
}
}
}
}
}
return false;
}
删除方法:如果表为空,返回false,如果仅有头结点,且头结点的值为key对应值,将表置为空,如果该表有多个节点,且对应的key值在头结点上,删除头结点,否则,遍历链表,寻找对应的key值,找到即删除,若表中没有对应的key值,返回false
HashMap和Hashtable区别和联系
1、继承的父类:Hashtable父类为Dicionary;HashMap父类为AbstractMap,都实现了Map、Cloneable、Serializable接口。
2、默认容量:Hashtable为11;HashMap为16
3、初始化机制:Hashtable为构造函数中初始化;HashMap在第一次put时初始化
4、并发操作:Hashtable使用同步机制,但在实际应用程序中,Hashtable本身的同步并不能保证程序在并发操作中的正确性,需要更高层次的保护;HashMap没有同步机制,需要使用者自己进行并发访问控制。
5、线程同步:HashMap线程不同步(效率高,安全性低);Hashtable线程同步(效率低,安全性高)
6、是否接受值为null的key或者value:Hashtable不接受;HashMap接受(但null为key值是只允许一个)
7、根据hash值计算数组下标的算法:Hashtable当数组长度较小时,并且key的hash值低位数值分散不均匀时,不同的hash值计算得到相同下标的概率较高;HashMap通过对key的hash做移位运算和位的与运算,使其能更广泛的分散到数组的不同位置。
8、数组遍历方式:Hashtable:Iterator和Enumeration;HashMap:Iterator.
9、是否支持fast-fall机制:Hashtable:用Iterator遍历时,支持,用Enumeration遍历,不支持;HashMap:支持fast-fall
10、Entry数组的长度:Hashtable:缺省初始长度是11,初始化时可指定initial capacity;HashMap:缺省初始化长度为16,长度始终保持2的N次方,初始化时可以指定initial capacity,若不是2的N次方,HashMap将选取第一个大于initial capacity的2的n次方作为其初始化长度。
11、LoadFactor负荷因子:均为0.75
12、负荷超过(LoadFactor数组长度)时,内部调整方式:Hashtable扩展数组:2原数组长度+1;HashMap扩展数组:原数组长度*2。两者都会根据key的hash值计算其在数组中的新位置,重新放置。算法相似,时间、空间效率相同。