介绍
- 实现了Map接口,存储的是键值对形式数据
- jdk1.8之前底层数据结构是数组+链表,jdk1.8之后是数组+链表+红黑树
- 非线程安全的
- key和value都可以为null值,key为null的数据存在数组下标为0 的位置
底层原理
底层数据结构
jdk1.8之前底层数据结构是数组+链表,jdk1.8之后是数组+链表+红黑树
重要属性
- 初始化容量:16
- 负载因子:0.75
- 阈值:容量*负载因子
- 扩容时机:当HashMap中的元素数量超过了负载因子(load factor)与当前容量的乘积时,就会触发扩容操作
- 树化时机:当HashMap的容量超过阈值(默认为64)且某个桶(bucket)中的元素个数超过8时,会触发树化操作。这是为了在高负载情况下提高查找效率,将链表结构转换为红黑树结构
- 反树化时机:当红黑树结构中的元素个数小于等于6时,会触发反树化操作。这是为了在负载较低的情况下,避免维护红黑树的开销,将红黑树结构转换回链表结构
- 扰动函数:
1.先用key的hashCode()值与它的高16位进行异或得到hash值
2、hash值再和初始容量-1进行与预算(h&(n-1))。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
新增元素过程
在JDK 1.7中,HashMap的新增元素过程如下:
1. 首先,根据键对象的hashCode()方法计算哈希码。
2. 使用哈希码和HashMap的容量计算数组索引,确定元素在内部数组中的存储位置。
3. 如果该位置上已经存在元素,则通过equals()方法比较键对象是否相等。如果键对象相等,则更新对应的值,否则发生了哈希冲突。
4. 如果发生哈希冲突,使用链表来解决冲突。新的键值对将被插入到链表的头部。
在JDK 1.8中,HashMap的新增元素过程进行了一些优化,引入了红黑树以及尾插法来提高性能:
1. 首先,根据键对象的hashCode()方法计算哈希码。
2. 使用哈希码和HashMap的容量计算数组索引,确定元素在内部数组中的存储位置。
3. 如果该位置上已经存在元素,则通过equals()方法比较键对象是否相等。如果键对象相等,则更新对应的值,否则发生了哈希冲突。
4. 如果发生哈希冲突,HashMap会先判断该位置上的链表是否已经转换为红黑树,如果是红黑树,则按照红黑树的插入方式进行插入。
5. 如果该位置上的链表没有转换为红黑树,则将新的键值对插入到链表的尾部。
6. 在插入新的键值对后,如果链表的长度超过阈值(默认为8),并且当前HashMap的容量超过了64,链表将被转换为红黑树。
红黑树在查找和插入操作上具有更好的时间复杂度,尾插法可以减少链表遍历的次数,提高插入的效率。
需要注意的是,JDK 1.8中的HashMap在处理哈希冲突时,会先尝试使用尾插法插入新的键值对,只有在链表长度超过阈值并且HashMap的容量超过一定大小时,才会将链表转换为红黑树。这样可以在大部分情况下避免不必要的红黑树转换,提高了性能和效率。
扩容过程
1.将数组容量扩大为原来的两倍
2、遍历原来数组中每个元素,重新计算下标后调用put方法