在HashMap的四个构造方法中,有一个构造方法传入的参数是一个Map类型的对象
/**
* 包含另一个“Map”的构造函数,包含另一个Map的映射,如果被映射的Map是一个null会抛出空指针异常。负载因子是默认的
* 直接传入存储了要添加进HashMap的key-value对的map,来构造HashMap
*/
public HashMap(Map<? extends K, ? extends V> m) {
//将默认的负载因子赋值给成员变量loadFactor
this.loadFactor = DEFAULT_LOAD_FACTOR;
//调用PutMapEntries()来完成HashMap的初始化赋值过程
putMapEntries(m, false);//以后的文章中会分析到这个方法
}
下面我们分析一下在传入Map参数的构造方法中调用的putMapEntries()方法。这个方法调用了HashMap的resize()扩容方法和putVal()存入数据方法。
putMapEntries方法会被HashMap的拷贝构造函数public HashMap(Map<? extends K, ? extends V> m)或者Map接口的putAll函数(被HashMap给实现了)调用到。该函数使用的是默认修饰符(default),也就是只有包访问权限,只能被本类或者该包下的类访问到,所以一般情况下用户无法调用。
/**
* 该方法的作用:将传入的子Map中的全部元素逐个添加到HashMap中
* @param evict 最初构造此Map时为false,否则为true(中继到afterNodeInsertion方法)。
*/
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
//获得参数Map的大小,并赋值给s
int s = m.size();
// 判断大小是否大于0 只有大于零Map中才有元素来插入HashMap
if (s > 0) {
// 判断table是否已经初始化 如果table=null一般就是构造函数来调用的putMapEntries,或者构造后还没放过任何元素
if (table == null) { // pre-size
// 如果未初始化,则计算HashMap的最小需要的容量(即容量刚好不大于扩容阈值)。这里Map的大小s就被当作HashMap的扩容阈值,然后用传入Map的大小除以负载因子就能得到对应的HashMap的容量大小(当前m的大小 / 负载因子 = HashMap容量)
// 先不考虑容量必须为2的幂,那么下面括号里会算出来一个容量,使得size刚好不大于阈值。但这样会算出小数来,但作为容量就必须向上取整,所以这里要加1。此时ft可以临时看作HashMap容量大小
float ft = ((float)s / loadFactor) + 1.0F;
//比较最大容量与ft,取小值; 到这里t暂时表示HashMap的容量大小。如果是将ft浮点型赋值给t整形,因为前面加了1.0f,这里也就实现了向上取整
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
// 只有在算出来的容量t > 当前暂存的容量(容量可能会暂放到阈值上的,刚使用构造函数构造出来的HashMap并且没有存入元素时,容量大小就会被暂时存在threshold中)时
// 才会用t计算出新容量,暂时存放到阈值上,在后面触发resize()扩容的时候会对threshold重新计算正确的阈值
if (t > threshold)
threshold = tableSizeFor(t);
}
//如果当前Map已经初始化,且这个map中的元素个数大于扩容的阀值就得扩容
//这种情况属于预先扩大容量,再put元素
else if (s > threshold)
resize();
//遍历map,将map中的key和value都添加到HashMap中
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
// 调用HashMap的put方法的具体实现方法putVal来对数据进行存放。该方法的具体细节在后面会进行讲解
// putVal可能也会触发resize
putVal(hash(key), key, value, false, evict);
}
}
}
注释里已经解释得很清楚了,这里再提几点重要的:
- if (table == null)分支,说明是HashMap的拷贝构造函数来调用的putMapEntries,或者是构造以后还没有放过任何元素,然后再调用putAll。
- float ft = ((float)s / loadFactor) + 1.0F这里的加1是因为,size / loadFactor = capacity,但如果算出来的capacity是小数,却又向下取整,会造成容量不够大,所以,如果是小数的capacity,那么必须向上取整。
- 算出来的容量必须小于最大容量MAXIMUM_CAPACITY,否则直接让capacity等于MAXIMUM_CAPACITY。
- if (t > threshold)这里的threshold成员实际存放的值是capacity的值。因为在table还没有初始化时(table还是null),用户给定的capacity会暂存到threshold成员上去(毕竟HashMap没有一个成员叫做capacity,capacity是作为table数组的大小而隐式存在的)。
- else if (s > threshold)说明传入map的size都已经大于当前map的threshold了,即当前map肯定是装不下两个map的并集的,所以这里必须要执行resize操作。
- 最后循环里的putVal可能也会触发resize操作。
参考资料:https://blog.csdn.net/anlian523/article/details/103639094