深入理解HashMap(五): 关键源码逐行分析之put
前言
上一篇我们讨论了HashMap的扩容操作, 提到扩容操作发生在table的初始化或者table大小超过threshold后,而这两个条件的触发基本上就发生在put
操作中。
本篇我们就来聊聊HashMap的put
操作。
本文的源码基于 jdk8 版本.
put方法
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
方法, 这个方法很重要, 我们将通过代码注释的方式逐行说明.
在这之前我们先看该方法的参数:
- hash
由上面的调用可知, 该值为hash(key)
, 是key的hash值, 关于hash的概念之前已经讲过了, 这里不再赘述.
- key, value
待存储的键值对
- onlyIfAbsent
这个参数用于决定待存储的key已经存在的情况下,要不要用新值覆盖原有的value
, 如果为true
, 则保留原有值, false
则覆盖原有值, 从上面的调用看, 该值为false
, 说明当key
值已经存在时, 会直接覆盖原有值。
- evict
该参数用来区分当前是否是构造模式, 我们在讲解构造函数的时候曾经提到,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;
}
总结
- 在put之前会检查table是否为空,说明table真正的初始化并不是发生在构造函数中, 而是发生在第一次put的时候。
- 查找当前key是否存在的条件是
p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))
- 如果插入的key值不存在,则值会插入到链表的末尾。
- 每次插入操作结束后,都会检查当前table节点数是否大于
threshold
, 若超过,则扩容。 - 当链表长度超过
TREEIFY_THRESHOLD
(默认是8)个时,会将链表转换成红黑树以提升查找性能。
(完)
查看更多系列文章:系列文章目录
Keep Coding
记录与分享
被 1 篇内容引用
推荐阅读
二分查找、二分边界查找算法的模板代码总结
二分查找作为程序员的一项基本技能,是面试官最常使用来考察程序员基本素质的算法之一,也是解决很多查找类题目的常用方法,它可以达到O(log n)的时间复杂度。
ChiuCheng赞 48阅读 34.4k评论 8
Java 编译器 javac 及 Lombok 实现原理解析
javac 是 Java 代码的编译器12,初学 Java 的时候就应该接触过。本文整理一些 javac 相关的高级用法。Lombok 库,大家平常一直在使用,但可能并不知道实现原理解析,其实 Lombok 实现上依赖的是 Java 编译器的注...
nullwy赞 10阅读 5.9k
与RabbitMQ有关的一些知识
工作中用过一段时间的Kafka,不过主要还是RabbitMQ用的多一些。今天主要来讲讲与RabbitMQ相关的一些知识。一些基本概念,以及实际使用场景及一些注意事项。
lpe234赞 8阅读 1.8k
Git操作不规范,战友提刀来相见!
年终奖都没了,还要扣我绩效,门都没有,哈哈。这波骚Git操作我也是第一次用,担心闪了腰,所以不仅做了备份,也做了笔记,分享给大家。问题描述小A和我在同时开发一个功能模块,他在优化之前的代码逻辑,我在开...
王中阳Go赞 5阅读 1.8k评论 2
Redis 发布订阅模式:原理拆解并实现一个消息队列
“65 哥,如果你交了个漂亮小姐姐做女朋友,你会通过什么方式将这个消息广而告之给你的微信好友?““那不得拍点女朋友的美照 + 亲密照弄一个九宫格图文消息在朋友圈发布大肆宣传,暴击单身狗。”像这种 65 哥通过朋...
码哥字节赞 5阅读 1.1k
NB的Github项目,看到最后一个我惊呆了!
最近看到不少好玩的、实用的 Github 项目,就来给大家推荐一把。中国制霸生成器最近在朋友圈非常火的一个小网站,可以在线标记 居住、短居、游玩、出差、路过 标记后可生成图片进行社区分享,标记过的信息会记录...
艾小仙赞 5阅读 1.5k评论 1
好好的系统,为什么要分库分表?
今天是《分库分表 ShardingSphere 原理与实战》系列的开篇文章,之前写过几篇关于分库分表的文章反响都还不错,到现在公众号:程序员小富后台不断的有人留言、咨询分库分表的问题,我也没想到大家对于分库分表的话...
程序员小富赞 3阅读 1.5k
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。