重写put方法
put 过程分析
// 当插入第一个元素的时候,需要先初始化数组大小
// 如果 key 为 null,感兴趣的可以往里看,最终会将这个 entry 放到 table[0] 中
// 1. 求 key 的 hash 值
// 2. 找到对应的数组下标
// 3. 遍历一下对应下标处的链表,看是否有重复的 key 已经存在,
// 如果有,直接覆盖,put 方法返回旧值就结束了
// 4. 不存在重复的 key,将此 entry 添加到链表中,细节后面说
public V put(K key, V value) {
// 当插入第一个元素的时候,需要先初始化数组大小
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
// 如果 key 为 null,感兴趣的可以往里看,最终会将这个 entry 放到 table[0] 中
if (key == null)
return putForNullKey(value);
// 1. 求 key 的 hash 值
int hash = hash(key);
// 2. 找到对应的数组下标
int i = indexFor(hash, table.length);
// 3. 遍历一下对应下标处的链表,看是否有重复的 key 已经存在,
// 如果有,直接覆盖,put 方法返回旧值就结束了
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 4. 不存在重复的 key,将此 entry 添加到链表中,细节后面说
addEntry(hash, key, value, i);
return null;
}
数组的初始化inflateTable
保证数组大小一定是2的n次方 new HashMap(20),那么处理成初始数组大小是 32
扩容阈值:capacity * loadFactor
计算数组的位置
使用 key 的 hash 值对数组长度进行取模就可以了。 简单的理解
static int indexFor(int hash, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return hash & (length-1);
}
简单说就是取 hash 值的低 n 位。如在数组长度为 32 的时候,其实取的就是 key 的 hash 值的低 5 位,作为它在数组中的下标位置
addEntry及扩容
如果当前 HashMap 大小已经达到了阈值,并且新值要插入的数组位置已经有元素了,那么要扩容
扩容的具体代码
在插入新值的时候,如果当前的 size 已经达到了阈值,并且要插入的数组位置上已经有元素,那么就会触发扩容,扩容后,数组大小为原来的 2 倍
扩容就是用一个新的大数组替换原来的小数组,并将原来数组中的值迁移到新的数组中
由于是双倍扩容,迁移过程中,会将原来 table[i] 中的链表的所有节点,分拆到新的数组的 newTable[i] 和 newTable[i + oldLength] 位置上。如原来数组长度是 16,那么扩容后,原来 table[0] 处的链表中的所有元素会被分配到新数组中 newTable[0] 和 newTable[16] 这两个位置。代码比较简单,这里就不展开了。
实际上面试别展开说 ,只说链表中的每个元素要重新进行rehash一下取模获得数组下标,进行重排