HashMap底层是个Node[]
成员变量:
DEFAULT_INITIAL_CAPACITY:默认初始化默认长度 1<<4 1左移四位也就是16
MAXIMUM_CAPACITY:最大长度 1<<30 1左移30位 也就是2的30次方 也就是1073741824(这个数字不好记,记得是2的30次方就行了,大概是10亿多)
DEFAULT_LOAD_FACTOR:负载因子,默认是0.75,这个参数用来表示当hashmap的长度达到 DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR 后就会进行resize(扩容)操作
TREEIFY_THRESHOLD : 默认是8,当桶上的链表数大于这个值是转红黑树。注意:桶的链表数(数组中某一元素的链表数),不是hashmap的长度
UNTREEIFY_THRESHOLD:默认值是6,当桶上的链表数小于这个值的时候转换成链表
MIN_TREEIFY_CAPACITY:默认值是64,最小树形化阈值,当hashmap的容量达到这个值时,才允许树形化,如果小于这个值直接扩容
Node<K,V>[] table :存放元素的数组
loadFactor:真正的负载因子,默认是DEFAULT_LOAD_FACTOR,可在创建hashmap的时候进行自定义 new HashMap(初始化长度,负载因子)
threshold:长度的阈值,hashmap数组(table)的长度 * DEFAULT_LOAD_FACTOR负载因子
put方法步骤:
1、如果hashmap值是空的,则将hashmap进行扩容(初始化成默认长度DEFAULT_INITIAL_CAPACITY,使用无参空构造构造方法创建的hashmap不会初始化数组)
2、key的hash值与(长度-1)进行 &(与) 运算(长度默认都是2的倍数,长度减一后所有位数都是1,高位补0),因此用来确定key所在数组中的位置,在这里jdk1.8进行了优化 1.7之前是与长度进行取%(模)运算,结果是一样的,与运算效率更高
3、如果数组的该位置为空,则创建Node 并将该node放入数组的对应位置
4、如果数组相应的位置不为空:并且key已经存在,覆盖原有的元素
5、如果数组对应的位置是红黑树,将元素添加到红黑树中
6、否则数组是链表,在链表的尾部(node的next)进行添加元素,如果该数组的位置链表长度已经达到了TREEIFY_THRESHOLD的值,则将该数组对应位置的链表转换为红黑树
7、如果key已经存在,返回旧值。
8、如果长度大于负载因子,则进行扩容
resize方法(扩容方法):
一、计算新的数组长度、阈值
1、使用oldCap记录旧数组的长度,oldThr记录旧数组长度的阈值
2、newCap为新数组的长度,newThr为新数组长度的阈值
3、如果旧数组的长度大于0并且达到最大值,则将长度阈值设置为Integer的最大值 也就是2的31次方
4、如果旧数组的长度大于0并且旧数组长度左移一位(也就是长度*2)后小于最大长度MAXIMUM_CAPACITY,并且大于等于默认长度16,newThr也有oldThr左移一位
5、如果旧数组的长度阈值大于0,新数组的长度为旧的阈值,否则新数组的长度和阈值都为默认值(初始化的时候)
6、如果新的数组长度和阈值有一个大于最大值MAXIMUM_CAPACITY,新的阈值就是Integer的最大值 也就是2的31次方
二、创建新的数组
三、将旧数组的值写入到新的数组
1、如果数组某个位置只有一个元素,将其hash值与新数组长度-1进行与运算并放入新数组的相应位置
2、如果是红黑树,重新初始化红黑树
3、循环链表,元素的hash值与旧数组长度进行与运算,针对不同的值拼接对应的链表,值为0,说明扩容后新数组对应的位置也一样,将链表的头放入新数组的对应位置,如果值不为0(值为旧数组的长度),
则对应新数组的位置为,旧数组的位置+就数组的长度