系列文章目录java
上一篇咱们讨论了HashMap的扩容操做, 提到扩容操做发生在table的初始化或者table大小超过threshold后,而这两个条件的触发基本上就发生在put
操做中。segmentfault
本篇咱们就来聊聊HashMap的put
操做。数组
本文的源码基于 jdk8 版本.app
HashMap 实现了Map接口, 所以必需要实现put方法:函数
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); /*final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) */ }
能够看到, put方法是有返回值的, 这里调用了 putVal
方法, 这个方法很重要, 咱们将经过代码注释的方式逐行说明.性能
在这以前咱们先看该方法的参数:this
由上面的调用可知, 该值为hash(key)
, 是key的hash值, 关于hash的概念以前已经讲过了, 这里再也不赘述.code
待存储的键值对接口
这个参数用于决定待存储的key已经存在的状况下,要不要用新值覆盖原有的value
, 若是为true
, 则保留原有值, false
则覆盖原有值, 从上面的调用看, 该值为false
, 说明当key
值已经存在时, 会直接覆盖原有值。get
该参数用来区分当前是不是构造模式, 咱们在讲解构造函数的时候曾经提到,HashMap的第四个构造函数能够经过已经存在的Map初始化一个HashMap, 若是为 false
, 说明在构造模式下, 这里咱们是用在put
函数而不是构造函数里面, 因此为true
。
参数解释完了以后, 下面咱们来逐行看代码:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 首先判断table是不是空的 // 咱们知道, HashMap的三个构造函数中, 都不会初始Table, 所以第一次put值时, table必定是空的, 须要初始化 // table的初始化用到了resize函数, 这个咱们上一篇文章已经讲过了 // 因而可知table的初始化是延迟到put操做中的 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 这里利用 `(n-1) & hash` 方法计算 key 所对应的下标 // 若是key所对应的桶里面没有值, 咱们就新建一个Node放入桶里面 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); // 到这里说明目标位置桶里已经有东西了 else { Node<K,V> e; K k; // 这里先判断当前待存储的key值和已经存在的key值是否相等 // key值相等必须知足两个条件 // 1. hash值相同 // 2. 二者 `==` 或者 `equals` 等 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // key已经存在的状况下, e保存原有的键值对 // 到这里说明要保存的桶已经被占用, 且被占用的位置存放的key与待存储的key值不一致 // 前面已经说过, 当链表长度超过8时, 会用红黑树存储, 这里就是判断存储桶中放的是链表仍是红黑树 else if (p instanceof TreeNode) // 红黑树的部分之后有机会再说吧 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //到这里说明是链表存储, 咱们须要顺序遍历链表 else { for (int binCount = 0; ; ++binCount) { // 若是已经找到了链表的尾节点了,尚未找到目标key, 则说明目标key不存在,那咱们就新建一个节点, 把它接在尾节点的后面 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保存的是目标key的键值对 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 到这里说明要么待存储的key存在, e保存已经存在的值 // 要么待存储的key不存在, 则已经新建了Node将key值插入, e的值为Null // 若是待存储的key值已经存在 if (e != null) { // existing mapping for key V oldValue = e.value; // 前面已经解释过, onlyIfAbsent的意思 // 这里是说旧值存在或者旧值为null的状况下, 用新值覆盖旧值 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); //这个函数只在LinkedHashMap中用到, 这里是空函数 // 返回旧值 return oldValue; } } // 到这里说明table中不存在待存储的key, 而且咱们已经将新的key插入进数组了 ++modCount; // 这个暂时用不到 // 由于又插入了新值, 因此咱们得把数组大小加1, 并判断是否须要从新扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); //这个函数只在LinkedHashMap中用到, 这里是空函数 return null; }
p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))
threshold
, 若超过,则扩容。TREEIFY_THRESHOLD
(默认是8)个时,会将链表转换成红黑树以提高查找性能。(完)
查看更多系列文章:系列文章目录