Q1、实例化一个HashMap ,桶数组是否已经初始化完成?
1、如果是空构造函数map,只设置默认负载因子
2、如果是带容量的map,初始化实例的只设置默认负载因子和阀值
3、如果是带容量和负载因子的map,初始化实例的只设置负载因子和阀值
总之构造完map后,如果构造方法没有设置负载因子,将已0.75的作为负载因子,如果设置了容量,将会设置阀值但是阀值的值就是(>=容量的2的几次幂的)最小数值,在put元素的时候,会调整容量*负载因子计算阀值
同时会根据容量实例化桶数组
Q2、扩容的是链表还是桶数组?
是桶数组进行扩容
Q3 、链表什么时候会转成红黑树?
某个链表的长度大等于8的时候,并且桶数组的长度为大于等于64时候是转成红黑树。
Q4、元素删除后,容量是否会缩减?
不会
Q5 、什么时候,会报并发修改异常?
在遍历的时候,如果元素有变化的时候
Q6、什么时候扩容
构造完map后,第一次往map里面放东西的时候
当放完元素后是新增的情况下,发现当前元素的个数大于hashmap的阀值的时候
Q7、扩容的时候,为什么元素不需要重新hash?
根据hash计算位置公式 我们假设扩容前的位置 index1=hash&len-1, 扩容后的位置为 index2=hash&(2*len)-1, 因此影响位置的位就是hash值位上面对应len最高位上的值,如果求得该位上面的值呢,可以hash&len得出扩容后参与运算位置上面值是0,还是还是1,如果是0,表示不影响扩容后的下标依然为index1,否则需要下标为(index2=index1+len).
例如某个值的hash值为 110111 ,原len为16,换算成位分布 len=10000 ,len-1 =01111 ,2len-1=011111
1 | 1 | 0 | 1 | 1 | 1 |
0 | 0 | 1 | 1 | 1 | 1 |
0 | 0 | 0 | 1 | 1 | 1 |
1 | 1 | 0 | 1 | 1 | 1 |
0 | 1 | 1 | 1 | 1 | 1 |
0 | 1 | 0 | 1 | 1 | 1 |
1 | 0 | 0 | 1 | 1 | 1 |
0 | 0 | 1 | 1 | 1 | 1 |
0 | 0 | 0 | 1 | 1 | 1
|
1 | 0 | 0 | 1 | 1 | 1 |
0 | 1 | 1 | 1 | 1 | 1 |
0 | 0 | 0 | 1 | 1 | 1 |
put 方法的源码分析:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0){ //1、如果桶数组为空,或者数组长度为0,进行扩容
n = (tab = resize()).length;
}
if ((p = tab[i = (n - 1) & hash]) == null){ //2 3、根据key计算在数组中的位置,取出第1个元素,如果第1个元素为空,则链表为空,则创建一个新节点,并将作为链表的第1个元素
tab[i] = newNode(hash, key, value, null);
}else { // 4、如果链表不为空(链表的第一个元素为空)
Node<K,V> e; K k; //e表示存在的节点,用于保存key相同的元素
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))){ 5、比较设置的元素和第1个元素,如果相同,设置存在变量为第一个元素
e = p; //e直接指向第1个元素
}else if (p instanceof TreeNode) // 6、不相同,并且是树形节点,进入树形节点的放入方法,否则遍历链表进 3
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else { //链表结构
for (int binCount = 0; ; ++binCount) { // 遍历链表,
if ((e = p.next) == null) { // 7、 遍历到最后一个节点(下一个节点为空),还是没找到 ,则在末端添加这个元素作为节点
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 8、添加元素后,遍历的长度已经是超过树化的阀值,进行树化。
treeifyBin(tab, hash); //
break;
}
if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) //9、在链表中存在相同key的节点,设置存在变量为找到的节点
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null){ //10、存在key相同的节点,节点值为null或者可以覆盖的话,将值覆盖老值
e.value = value;
}
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize(); //如果是新增,并且大于阀值的话,进行扩容
afterNodeInsertion(evict);
}
put方法的流程图
resize方法源码分析
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table; //1、桶数组
int oldCap = (oldTab == null) ? 0 : oldTab.length; //2、如果桶数组为空,则就的容量为0,否则为原数组的长度
int oldThr = threshold; //阀值
int newCap, newThr = 0;
if (oldCap > 0) { //3、原数组不为空,
if (oldCap >= MAXIMUM_CAPACITY) { //3.1 、判断是否大于2^30次方,大于则调整为2^31-1,否则只扩展阀值,不扩容量。
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //3.2 容量改成原来的2倍,阀值改成原来的2倍
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold, //3.3 原来的阀值大于0,容量却为0,第一次初始化hashmap是带容量的,新的容量就是老的阀值。
newCap = oldThr;
else { // zero initial threshold signifies using defaults //3.3 没有声明容量的map,新容量就就是默认初始容量16,阀值为12(16*0.75)。
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) { //根据新容量计算新阀值
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) { //遍历桶数组
Node<K,V> e;
if ((e = oldTab[j]) != null) { //第一个元素不为空
oldTab[j] = null; //将第一个元素设置为空
if (e.next == null){ //链表中只有一个元素
newTab[e.hash & (newCap - 1)] = e;//计算节点位置,放在新数组的第一个元素里面
}else if (e instanceof TreeNode){
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
}else { // preserve order //如果后面还有节点
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) { //hash值中的计算新位置位上面为0,不影响扩容后的位置
if (loTail == null) //如果是末节点为空
loHead = e; //当前节点作为首节点
else
loTail.next = e; //否则设置为末节的的下一个节点
loTail = e; //并设置当前节点为末节点
}
else { //hash值新增1位参与计算的值为1
if (hiTail == null) //新位置的最新的最后一个节点为空,则是第一个节点
hiHead = e;
else
hiTail.next = e; //否则设置为末节的的下一个节点
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) { //老位置的末节点不为空,则设置末节的的下一个节点为空
loTail.next = null;
newTab[j] = loHead; //老的位置的首节点为头结点
}
if (hiTail != null) { //新位置的末节点不为空,则设置末节的的下一个节点为空
hiTail.next = null;
newTab[j + oldCap] = hiHead; //指向新位置
}
}
}
}
}
return newTab;
}