初始化
构造方法
可见,HashMap有四种构造方法:
其中1、3、4可以归为一类:使用默认的或者指定的初始化容量和负载因子,如果使用默认容量16,则会在第一次插入时在 resize 中自行计算 threshold。如果自行指定参数则直接赋值(通过 tableSizeFor 方法扩容到与 initialCapacity 最接近的 2 的幂次方大小) threshold,然后进行扩容判断。
// 默认构造函数。
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
// 包含另一个“Map”的构造函数
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);//下面会分析到这个方法
}
// 指定“容量大小”的构造函数
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 指定“容量大小”和“负载因子”的构造函数
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
// 初始容量暂时存放到 threshold ,在resize中再赋值给 newCap 进行table初始化
this.threshold = tableSizeFor(initialCapacity);
}
其中第二种使用其他Map进行初始化的方法如下:
1、如果还没有初始化,则先通过负载因子和传入map的元素个数计算所需的最小容量。然后进行最大上界判断。如果最小容量大于threshold,那就初始化为 tableSizeFor 的结果。这里的 if (t > threshold) 实际上永远都为真,因为未初始化时 threshold = 0,使用这个同之前一样是为了之后扩容 resize 做准备
2、初始化了则进行判断并resize
3、最后放入到新的map中,如果是未初始化的table会在putVal中resize初始化扩容。
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
// 判断table是否已经初始化
if (table == null) {
// pre-size
/*
* 未初始化,s为m的实际元素个数,ft=s/loadFactor => s=ft*loadFactor, 跟我们前面提到的
* 阈值=容量*负载因子 是不是很像,是的,ft指的是要添加s个元素所需的最小的容量
*/
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
/*
* 根据构造函数可知,table未初始化,threshold实际上是存放的初始化容量,如果添加s个元素所
* 需的最小容量大于初始化容量,则将最小容量扩容为最接近的2的幂次方大小作为初始化。
* 注意这里不是初始化阈值
*/
if (t > threshold)
threshold = tableSizeFor(t);
}
// 已初始化,并且m元素个数大于阈值,进行扩容处理
else if (s > threshold)
resize();
// 将m中的所有元素添加至HashMap中,如果table未初始化,putVal中会调用resize初始化或扩容
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
put方法
jdk1.8
HashMap 只提供了 put 用于添加元素,putVal 方法只是给 put 方法调用的一个方法,并没有提供给用户使用。
- table未初始化或者长度为0,进行扩容
- (n - 1) & hash 确定元素存放在哪个桶中,如果桶为空,则直接生成新结点放入桶中
- 桶中存在元素,则发生了hash冲突,判断第一个结点的key和插入key是否相同,如果相同则用新的value值替换掉旧的value值。
- 如果第一个结点是红黑树结点,则调用方法插入到树中
- 不是树节点则开始遍历链表找是否存在相同的key来替换或插入
- 如果找到key相同的结点则停止遍历,并替换value值
- 没有找到,则在链表尾部插入新结点
- 更改modCount和size并判断是否需要进行扩容
public V put(K key, V value) {
return putVal(hash(key), key, value,