继承了abstractMap, 实现了map、cloneable、serializable接口

初始参数

默认初始化容量16

最大容量1<<30(约10亿)

默认负载因子0.75

阈值为容量*负载因子

结构被修改次数modCount(修改val不算)

构造函数

hashMap(初始容量,负载因子)

hashMap(初始容量):加上默认负载因子

hashMap(): 默认初始容量,默认负载因子

hashMap(Map): 新建一个hashMap, 容量为(传入的map的容量/负载因子 + 1)和默认容量的最大值,并用map的元素填充新hashMap

方法

hash(h):静态,默认访问权限。
这个有点儿复杂,只是记下h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);

indexFor(h, length): 静态,默认访问权限。
h&(length-1) 这里可以看出容量选择2的幂的原因了:lenght-1之后二进制所有位都是1(除最高位以上),进而有快速屏蔽h高位的效果

get(key): 对于key为null,直接查找table数组的第一位(下标为0);非null的键,先hash出在table数组中的下标,再检索Entry链表

put(key, value): 对于key为null,放到table数组的第一个。非null的key,若存在,更新val,返回原始值;不存在则新建entry,将table原来该位置的entry链放到新entry的后边(即头插法,听说jdk8采用尾插法)。元素个数大于阈值,resize扩大一倍长度

resize(newCapacity): 默认访问权限。
如果容量已经是最大容量则该阈值为Integer最大值,不再扩容。否则,创建新数组,将原来的元素rehash放入数组。

这里会有一个问题(面试官会考的哦),就是链表会出现无线循环。问题出在这行代码 e.next = newTable[i];上,e结点位于原数组上的一条链表,newTable[i]是对应的扩容后的新数组上的链表(对应的新链表)的头结点(随着扩容的进行,原链表的元素从头到尾,往新链表的头部进行插入)。

当两个线程同时进行resize的时候就存在这样一种情况:e.next = e(死循环),也就是newTable[i]=e了。为什么会出现这种情况呢?
想象一下只有一个线程的情况,代码执行到下一行才会将e赋值给newTable[i], 也就是newTable[i]指向了已e开头的新链表。也就不会存在e=e的情况。

为啥多线程就会有问题了呢?就是因为存在另一个线程会在当前线程准备执行e.next = newTable[i]的时候,提前执行了newTable[i]=e。所以就有了死循环。

02-10 18:17