一、概述
HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长.。HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。
线程不安全;
可以接受null键值和值,而Hashtable则不能;
查找效率非常高,因为它使用Hash表对进行查找,可直接定位到Key值所在的桶中;
二、主要参数
1、容量(数组长度):初始长度为16,其次在自动扩展或手动初始化时,长度必须是2的幂。
2、阀值(threshold)=容量(capacity,初始为16)*加载因子(load factor默认为0.75)=12,即添加第13 个键值对 <Key,Value>的时候,map的容量扩充一倍;
(为啥容量为16而加第十三个时就要扩充容量:尽可能第减少桶中的Entry<Key,Value>链表的长度,以提高HashMap的 存取性能)
三、数据结构
主要为:数组+链表(+红黑树)(右图:当链表长度超过 8 时,链表转换为红黑树。)。
为什么转换为红黑树:
链表的长度非常长时,查找链表操作的时间复杂度是很高的,为O(n);
四、工作原理
HashMap<Integer, Integer> hashMap = new HashMap<>();
HashMap 底层是 hash 数组和单向链表实现,数组中的每个元素都是链表,由 Node 内部类(实现 Map.Entry<K,V>接口)实现,HashMap 通过 put & get 方法存储和获取。
注意:第二次在同一位置put会覆盖原来的value。
1、存储对象(详细流程代码):
①、调用 hash(key.hashcode) 方法计算 K 的 hashcode 值,然后结合数组长度,计算得数组下标(即在哪个桶);
/**
* 将<Key,Value>键值对存到HashMap中,如果Key在HashMap中已经存在,那么最终返回被替换掉的Value值。
* Key 和Value允许为空
*/
public V put(K key, V value) {
//1.如果key为null,那么将此value放置到table[0],即第一个桶中
if (key == null)
return putForNullKey(value);
//2.重新计算hashcode值,
int hash = hash(key.hashCode());
//3.计算当前hashcode值应当被分配到哪一个桶中,获取桶的索引
int i = indexFor(hash, table.length);
//4.循环遍历该桶中的Entry列表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//5. 查找Entry<Key,Value>链表中是否已经有了以Key值为Key存储的Entry<Key,Value>对象,
//已经存在,则将Value值覆盖到对应的Entry<Key,Value>对象节点上
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {//请读者注意这个判定条件,非常重要!!!
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//6不存在,则根据键值对<Key,Value> 创建一个新的Entry<Key,Value>对象,然后添加到这个桶的Entry<Key,Value>链表的头部。
addEntry(hash, key, value, i);
return null;
}
/**
* Key 为null,则将Entry<null,Value>放置到第一桶table[0]中
*/
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
②、调整数组大小(当容器中的元素个数大于 阀值 时,容器会进行扩容resize 为 2的n次幂)
步骤:
1. 申请一个新的、大小为当前容量两倍的数组;
2. 将旧数组的Entry[] table中的链表重新计算hash值,然后重新均匀地放置到新的扩充数组中;
3. 释放旧的数组;
2、获取对象
①、调用 hash(key.hashcode) 方法计算 K 的 hashcode 值,从而获取该键值,所链表的数组下标(即在哪个桶);
②、顺序遍历链表,equals()方法查找相同 Node 链表中 K 值对应的 V 值。
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
//遍历列表
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
其他
1、.containsKey(Object key)方法,返回值为boolean,用于判断当前hashmap中是否包含key对应的key-value;
containsValue(Object value)方法,返回值为boolean,用于判断当前hashmap中是否包含value对应的ke
2. 键值对Entry<Key,Value>的移除----remove(key)方法的实现