最近做项目的时候,添加了阿里的代码规范扫描插件
扫描的时候指出我的HashMap未指定初始化大小,这是不规范的
推荐:HashMap集合初始化时, 指定集合初始值大小。
网上给的解释是:
不指定的话,hashMap的每次put,会去判断是否到达HashMap的极限值(桶的大小*0.75(负载因子) ),到达即调用resize方法,如果频繁的调用resize会影响性能
注意:桶大小不等于size,可能桶的值已经32了,而size只有15,size表示HashMap中条目(即键-值对)的数量。
HashMap关键属性
//默认的桶数组大小=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大size(超过这个值就将threshold修改为Integer.MAX_VALUE(此时桶大小已经是2的31次方了),表明不进行扩容了)
static final int MAXIMUM_CAPACITY = 1 << 30;
//负载因子(后面很多地方会用到)
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//无参构造方法
public HashMap() {
This.loadFactor = DEFAULT_LOAD_FACTOR; //设置一个默认的负载因子,默认为0.75
}
Map<Object,Object> response=new HashMap<Object,Object>(int initialCapacity);
如果暂时无法确定hashMap的size,那么取默认值16
如果已知要存的大小,那么可以根据下面的式子求出预存值
式子: initialCapacity = (预备存储的大小 / 负载因子) +1
Node<K,V>[] tab; Node<K,V> p; int n, i;
//table就是桶数组,初始为null
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
2.然后调用resize()扩容
else{
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
newCap的意思是:将用于hash的桶数组的长度,这里的默认值是16
newThr的意思是:当桶中的键值对的个数超过这个值时就进行扩容,这时候上面提到的负载因子就起作用了,所以这里的newThr为0.75×16 = 12
3.putVal()中判断hash桶是否需要扩容
if (++size > threshold)
resize();
4.第一次之后put执行的code
//肯定会进入这个分支
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//如果我们是这样调用:HashMap(7(任意满足条件的值)),那么经过第一次调用put()会初始化桶数组的大小和极限值一样,这里即为8。所以这里不会进入这个分支,但是newCap会变为16 判断条件处保证了无论是size 还是 threshold都是2的幂
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
//所以会进入这里
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;//计算的出这里的threshold即阈值会由刚才的8变为12
5.为什么HashMap容量一定要为2的幂?
(涉及到结构)目的就是为了让HashMap的元素存放更均匀。最理想的状态是,每个Entry数组位置都只有一个位置,即next没有值,也就是没有单链表,这样查询效率高,不用遍历单链表,更不用去用equals比较K。一般考虑分布均匀,都会用到%(取模),哈希值%容量=bucketIndex。
6.是否会出现size无限扩容问题
答案是不会,cap最大为Integer.MAX_VALUE;
//每次都加倍,所以最终肯定会加倍到MAXIMUM_CAPACITY,会进入这个分支。
if (oldCap >= MAXIMUM_CAPACITY)
{ threshold = Integer.MAX_VALUE;
return oldTab;
}
//这个size类型是int,那么最大就是Integer.MAX_VALUE,所以不会有++size > threshold的情况,往后都不会进行扩容了。
if (++size > threshold)
resize();
测试:Integer.MAX_VALUE++ 不会报错,但是也无法进入if代码块
核心代码解析
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;//HashMap无参构造函数后table是Null
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;//默认构造器的情况下为0
int newCap, newThr = 0;
if (oldCap > 0) {//桶已扩容
//当前桶容量大于最大值得时候返回当前table
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//桶的容量乘以2,桶阈值也乘以2
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
//使用带有初始容量的构造器时,table容量为初始化得到的threshold
newCap = oldThr;
else { //默认构造器下进行扩容
// zero initial threshold signifies using defaults
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) {
//对新扩容后的桶进行赋值,条件中的代码删减
}
return newTab;
}