HashMap1.8 之新增/修改put
一.引读
这篇意在分析HashMap1.8源码的put()方法。对于基础数据结构的操作,无非就是增删改查,而这篇我们要阐述的就是JDK1.8里HashMap里的新增/修改,也就是put()方法。
对于对HashMap还不熟悉的读者,建议先看下我之前写的拙作:
1.HashMap源码解读之原理浅析—前传篇
2.HashMap1.8-源码浅析1之成员变量及2个静态内部类阐述
3.HashMap1.8-源码浅析2之初始化
看下我们平常的使用:
1-1:
public void test(){
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("key", "value");
}
注:在以下的展示的代码片段中,若是JDK的源码,我会copy出其方法的注释,并翻译出来,有些还会加上我自己的理解
二.HashMap put()中的hash()
上面 hashMap.put(“key”, “value”); 跟着往下,如下所示:
2-1:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
* 注释翻译:
* 将指定值与此映射中的指定键相关联。如果映射以前包含键的映射,则旧的值被替换。
* @param key 要与指定值关联的键
* @param value 要与指定键关联的值
* @return 键相关的上一个值,不存在(没有映射)或为空时返回null
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
接着看2-1中的hash()方法:
2-2:
/**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
* 注释翻译:
* 计算key.hashCode()并传播(XORs)更高的散列位降低。因为该表使用2的幂掩蔽,所以仅在当前掩码
* 上方以位变化的散列将总是发生碰撞。(已知的例子有一组浮点键在小表格中保存连续的整数。)
* 所以我们应用扩展更高位影响的转换向下。在速度、效用和bit-spreading质量。
* 因为有很多常见的哈希集合已经被合理分配了(所以不能从中受益)
* 因为我们使用树来处理大的集合在箱子里的冲突,我们只是异或一些移位的位以最便宜的方式减少系统损失,
* 以及以吸收最高位的影响,否则由于表边界的关系,永远不要在索引计算中使用。
*
* 个人理解:简单来说,就是我们平常得到的HashCode()值用二进制表示(32位),有更大的可能大部分的数是在16位及以下,
* 高位的16位的数比较少,所以用低16位和高16位这种进行异或(h = key.hashCode()) ^ (h >>> 16),加入高位的特征,
* 使其更加的分散,从而减少hash的冲突
* 简而言之,为了减少hash冲突
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里还值得一说的就是,若key为null,那么其hash值一直都是0;
三.HashMap put()方法的实现 putVal()
3-1:
/**
* Implements Map.put and related methods.
*
* @param hash hash for key
* @param key the key
* @param value the value to put
* @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
*
* 注释翻译:
* @param hash key的hash值
* @param key key
* @param value value
* @param 若onlyIfAbsent = true, 那么不替换已经存在的value值
* @param 若evict = false, 那么表示 表处于创建模式,这个字段用于扩展的这里没有什么用
* @return 先前已有的映射的值,若没有或为null则是null
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断HashMap成员变量中的table是否存在
if ((tab = table) == null || (n = tab.length) == 0)
//若HashMap成员变量中的table为空则new出table
n = (tab = resize()).length;
//对key的hash值对table的length取模,并以p记录下来取模后的头节点,判断头节点是否为空
if ((p = tab[i = (n - 1) & hash]) == null)
//如果为空,说明还没有节点,那么把要放进来的key,value包装成一个头节点放入到里面
tab[i] = newNode(hash, key, value, null);
else {
//若进入这里说明 有hash冲突
Node<K,V> e; K k;
//判断放进来的key是否与已存在的头节点的key相同
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//若相同,则用e记录下来
e = p;
//判断已存在的头节点是树节点
else if (p instanceof TreeNode)
//若是则以树节点放入的方式去处理,这里不详细说明,以后详细分析
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//若进入到这里说明,有hash冲突,并且和头节点的key不相同,然后遍历这条链
for (int binCount = 0; ; ++binCount) {
//判断上一个遍历节点还有没有下一个节点
if ((e = p.next) == null) {
//若没有,这说明已遍历完链上的节点,没有找到存在相同key的节点,那么把放进来的
//key和value包装成一个节点对象,并放到链的最后面
p.next = newNode(hash, key, value, null);
//判断放完新的节点后,链上的节点树是否已达到TREEIFY_THRESHOLD(树化的阈值这里是8)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
//如果是,则考虑去树化,
//这里面的逻辑并不是直接树化了,还看下此时hashMap的capacity(容量)是否已达到了64
//若达到了64 才会去树化,否则是扩容
treeifyBin(tab, hash);
break;
}
//判断是否与遍历到的节点的key是否相同
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
//是相同则找到了已存在相同key的节点,跳出循环
break;
//把当前遍历到的节点e给p记录下来
p = e;
}
}
//因为上面遍历了一轮之后e记录的就是 与放入进来的key相同的已存在节点
if (e != null) { // existing mapping for key
//这里进来说明已找到存在相同key的节点,并且是e
V oldValue = e.value;
//看onlyIfAbsent值为false或者 存在相同key的节点对应的老value值为空,
//那么用放入的新的value替换掉老value
//注意这里若已存在相同key的节点,但oldValue == null则也会被替换掉
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
//返回存在相同key的节点对应的value值
return oldValue;
}
}
//变更次数+1
++modCount;
//判断已放入的节点数是否超过了节点数阈值
if (++size > threshold)
//若大于则扩容
resize();
//HashMap这里没啥用,用于扩展,比如LinkedHashMap
afterNodeInsertion(evict);
return null;
}
总结:看我HashMap源码解读之原理浅析—前传篇就好,里面有思想的总结。
好了以上就是put()方法的整体流程了!