下面是对HashMap中put方法的源码进行注释
测试代码
/**
* 测试put操作的区别
*/
@Test
public void put(){
HashMap<String, String> map = new HashMap();
map.put("aa","aa");
}
JDK 7
public V put(K key, V value) {
//校验key是否为空
if (key == null)
return putForNullKey(value);
int hash = hash(key); //获取key对应的hash值
int i = indexFor(hash, table.length); //得到该KV对应的table的index
//这个for循环就是在校验table[i]对应的链表中要插入的K key有没有存在,如果有,那么就用put的 value替换,然后返回该key对应的老的value
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++;//修改次数+1
addEntry(hash, key, value, i); //确定key没有重复之后,插入(K,V)
return null;
}
点击 addEntry
方法
void addEntry(int hash, K key, V value, int bucketIndex) {
//判断是否需要扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0; //扩容后,对应的hash需要重新计算
bucketIndex = indexFor(hash, table.length);//扩容后,对应的bucketIndex需要重新计算
}
//判读是否需要扩容后,插入
createEntry(hash, key, value, bucketIndex);
}
点击 createEntry
方法
void createEntry(int hash, K key, V value, int bucketIndex) {
//采用头插法插入(由于上面已经对key是否存在校验过了,所以这里保证了HashMap中要插入的key是不存在的,所以这里采用头插法是没有问题的)
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
JDK 8
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
点击 putVal
方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
/**
* Node<K,V>[] tab 指向HashMap中table的指针
* Node<K,V> p table[i]指向的对象
* n table的长度
* i p在table对应的index
*/
Node<K,V>[] tab; Node<K,V> p; int n, i;
//如果还没初始化就初始化
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//如果插入到bulkindex对应的table的slot还没有值,那么直接将(K,V)封装的newNode对象赋给tab[i]
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//如果插入的key跟table[i]的key相同,那么将table[i]的slot的值(node)赋给e
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//否则,如果table[i]是红黑树类型,则调用红黑树的插入方法
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//否则,table[i]是链表
else {
for (int binCount = 0; ; ++binCount) {
//采用尾插法插入新的node
if ((e = p.next) == null) { //注意一个很隐蔽的操作,e已经赋值为p.next了。开始遍历链表,e就是临时记录node的指针
p.next = newNode(hash, key, value, null);
//如果链表的长度大于指定的长度,默认是8,则转换成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果链表中有存在跟插入的node的key和hash是一样的话,那么e指定该节点,然后退出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; //下个节点
}
}
//说明有key相同的node
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount; //修改次数+1
if (++size > threshold) //插入后判断是否需要扩容
resize();
afterNodeInsertion(evict);//兼容LinkedHashMap的方法,针对HashMap不用管
return null;
}
总结
JDK 7 的插入操作是先判断key是否已经存在,
如果存在,替换value,返回old value
如果不存在,头插法插入
JDK8 的插入先判断是否存在,
如果存在则替换value,用一个Node e指向old node,用来后续判断
如果是不存在,直接在尾部插入该node
如果是红黑树,那么调用红黑树的put方法