说的是JDK1.8的
扩容:
三情况会扩容:
1、第一次put元素创建数组的时候。
2、插入节点后元素个数大于N*负载因子的时候。
3、数组小于64但是链表节点大于8。
大白话版本:
Java数组是不可变的,先创建一个2倍的数组(用n << 1)左移一位的优化计算方式。
把原数组上的节点放在新数组上。根据规律rehash优化,扩容后节点要么在原位置,要么在原来的位置+数组原长度 原来的位置连接在low链上,需要移动 i+n的在High链上。把链表挂在新数组上。
原来的Index可能是树类型,移动后长度可能小于6(hashmap)有个常量,如果长度小于6就会把树转回链表。
put流程:
hashmap的Hash算法是先把key的hashcode与自己右移16为异或运算,得到的hash值在和数组长度-1&与运算定位下标。
大白话版本:
1、先看数组是否创建了,没创建就调用resize() 方法扩容
2、把 (n - 1) & Key的HashCode 定位下标 ,为null、直接插入就行
3、下标位置不为null,用equals()比较key一不一样,一样就覆盖
4、否则instanceof关键字看这个tab[i]位置是不是树类型节点,是树的节点就调用putTreeVal()按照红黑树的插入方式插入
5、不是树的节点就说明他还是链表就遍历链表(遍历人间过程还会比较key)尾插法插入链表节点
6、链表插入节点后,会看长度是否大于了8并且数组的长度不小于64
7、满足6的条件的话链表转红黑树
8、插入完成后看是否需要扩容
解释:
1.8的数组创建(调用resize方法)放在了第一次put元素的时候。
计算下标的时候(n - 1) & Key的HashCode来定位,这引出两个问题:当key没有重写HashCode方法时,两个一样的key(不同的两个对象)基本不会定位到同一个下标,就不能在遍历这个下标位置所有key 的时候比较去重。数组的长度是“2的n次幂”原因是这个长度可以用(n-1)&hashcode代替n%hashcode 。计算机硬件结构%很慢,没有专门的除法器,&就很快。
插入节点的时候链表有链表的插入方法,树有树的插入方法。
1.8改为了尾插法,因为之前头插法扩容的时候访问会出现死循环链,之前头插法的原因是认为最近插入的最近访问频率也高,但是扩容后,链表位置又倒置了,不如直接改成尾插了。
链表不是大于8了就一定转树,当数组长度不够64的时候会先扩容
插入节点后看是否需要扩容,(做LRU缓存的时候也是插入后看需不需要移除元素)