Java8 put源码解析,大致可分为如下步骤:
一:判断是不是第一次插入,如果是,则进行resize()
二:如果通过hash&n-1计算出来的下标,里面如果没有元素,则在改下标下创建一个新的元素,然后put操作完成,然后判断是否扩容
三:如果计算的下标下面已经存在元素,通过key值进行判断是否相同,如果相同,则结束,判断操作,在后去代码中,将新的值,覆盖为老的值,并返回老的值
四:如果下标存在的值与要插入的元素的key值不相同,则进入循环,因为在hashmap中,一个下标下的元素可能有多个值,原因是不同的key值在进行hash后的值可能相同,所以在同一个下标下,可能有多个key值的hash相同,key不相同的元素.所以要进行循环,直到查到对应的元素,或者查询到节点下最后一个元素,才会跳出循环,在一个下标下,如果元素的个数小于8则是以链表的形式进行存储的,如果大于8则是以红黑树进行存储的
五:如果插入的是新元素,则判断插入后的容量是否,已经超过了当前容量,如果超过了,则进行扩容
源码如下:
put方法:
public V put(K key, V value) {
//调用putVal
return putVal(hash(key), key, value, false, true);
}
putVal方法:
/**
* Implements Map.put and related methods
*
* @param hash hash for keyput
* @param onlyIfAbsent if true, don't change existing value
* @param evict if false, the table is in creation mode.
* @return previous value, or null if none
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
/**
* 判断tab是不是为空,如果为空,则将容量进行初始化,也就是说,初始换操作不是在new HashMap()的时候进行的,而是在第一次put的时候进行的
* 这一块是有雄线程问题的.
*/
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
/**
* 初始化操作以后,根据当前key计算当前值的下标,如果下标对应的位置,没有元素,则将值放到这个位置下,并将,当前节点的元素赋值给p
*/
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
/**
* 如果对应的位置已经有元素的情况,走到这一步,说明元素 P 不为空,且不等于null
*/
else {
Node<K,V> e; K k;
//判断这个这个元素的key是否与要放进来的的key是否相同,如果相同,则把这个当前这个位置的元素赋值给e,用于后期判断
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//如果key不相同,则判断当前节点是不是TreeNode(红黑树),如果是,则进行红黑树的插入
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//如果不是红黑树,怎进行链表操作
else {
//binCount 是为了计算,当前hash的位置下,元素的个数,用于判断binCount >= TREEIFY_THRESHOLD - 1,如果成立,则转换成红黑树
for (int binCount = 0; ; ++binCount) {
/**
代码运行 if ((e = p.next) == null) 里面的语句,说明,已经循环到的最后一个元素
判断当前元素的下一个节点是不是为空,其实就是为了判断,当前元素是不是,该下标下的最后一个元素的语句
如果是最走一个元素,则将要插入的新元素,放到当前元素的后面
然后判断,当前下标下的元素个数是是否>=8个,如果大于则将当前下标下的所有元素,从链表改成红黑树,并跳出循环
*/
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//判断元素个数是不是>=8
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//转换成红黑树
treeifyBin(tab, hash);
break;
}
/**
* 如果不是当前下标下的最后一个元素,并且找到了,与插入的数据的key相同的元素,则跳出循环,e 的赋值在上一次的循环中已经赋值过了
*/
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
/**
把当前下标下,找到的节点,赋值给p并进行下一次循环
走到这一步,说明,在循环当前下标下元素的key时,还没有到结尾,并且也没有找到,与要插入的元素的key相同的元素,所以,继续循环
*/
p = e;
}
}
/**
e != null 说明,根据key的hash值算出来的下标下有元素,原因是,在上面的循环过程中,如果有元素,e都不为空
只要e不为空,说明e的key与要插入的元素的key是相同的,判断是否覆盖onlyIfAbsent,然后返回原来oldValue
如果走下面这些代码块,则说明,没有新的元素插入,不需要进行容量判断
*/
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
/**
* 如果走到下面这些代码,这说明有新的元素插入,因为如果key值已经存在,在上面的代码中已经返回了,根本不会走到下面.
* 判断容量是否已经到了需要扩充的阈值了,如果到了,则进行扩充
*/
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}