浅谈 HashMap(二):put 插入方法源码分析
本文基于 JDK1.8
这节讲讲 HashMap 的 put 方法,看似简单的一个存值方法,实则细节满满,且听我娓娓道来
话不多说,先看看源码👇
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); }
方法内部又调用了一个 putVal 方法,一共 5 个参数,我们先猜猜这几个参数是干嘛的。第一个参数是根据 key 调用了hash()
方法,此参数应该是用来确定 key 在 table(这个属性参照上一节)数组中的位置的;第二个参数就是传入的 key 了;第三个参数即传入的 value 了,下面我们通过源码来看看👇
/** * Implements Map.put and related methods * * @param hash key的hash值 * @param key 键 * @param value 准备插入的值 * @param onlyIfAbsent 如果为true,则不修改已存在的值 * @param evict 如果为false,则table数组处于创建模式 * @return 旧的值,如果没有则返回null */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { // 定义局部变量,接收table数组 // 这种全局变量赋值给局部变量操作的写法,是一种编码习惯,便于代码可读性及调试 Node<K,V>[] tab; // 局部变量,接收table数组位置上的Node节点(如果已存在节点) Node<K,V> p; // 局部变量 n,table数组长度 // 局部变量 i,根据hash值(由key计算而来,后续讲)确定在table数组中的位置 int n, i; // 上节讲到table数组在第一次使用时初始化,源于此处 // 如果table数组为空,则通过resize方法初始化 if ((tab = table) == null || (n = tab.length) == 0) // resize方法后续有专题讲 n = (tab = resize()).length; // 判断当前table数组位置是否已有值 // 如何根据hash值确定在table数组中位置的呢?后续有专题讲 if ((p = tab[i = (n - 1) & hash]) == null) // 如果table数组此位置没有值,则新建Node节点存于此处即可 tab[i] = newNode(hash, key, value, null); // 如果table数组此位置已经有值,则执行下面代码 else { // 局部变量,接收已存在的Node节点 Node<K,V> e; // 局部变量,接收键(key) K k; // 判断key值是否相等,相等则将原Node赋值给局部变量 e if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 判断已有节点的存储形式是否为 tree(红黑树),如果是tree类型,则给红黑树中新增一个Node(红黑树的增删此处不讲) else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 除去上面两种情况,则table数组此位置的节点为链表存储形式 else { // 从此处循环可知,HashMap在JDK1.8的链表插入为 [尾部插入] for (int binCount = 0; ; ++binCount) { // 循环遍历链表,直到最后一个Node,新建一个Node存值 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; } // 遍历链表时,判断链表中已存在Node的key是否与插入的key相等 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } // 判断插入的key是否为已存在的key if (e != null) { // existing mapping for key // 从此处可知,前面为何要定义两个局部变量 p 和 e,为了返回旧的value值 V oldValue = e.value; // 根据传入的参数,决定是否修改已存在的值 if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); // 如果插入的key已存在,返回已存在的旧的value值 return oldValue; } } // HashMap的全局变量,每次操作时加 1 ++modCount; // 先对HashMap的容量 +1,判断是否达到扩容阈值(最大容量 * 负载因子0.75) if (++size > threshold) // 扩容,重置HashMap大小,后续有专题讲解 resize(); afterNodeInsertion(evict); // 如果插入的key是新的,则返回null;对比上面,如果key已存在,则返回旧的value return null; }
put 方法的源码如上,笔者已经添加了详细的注释。其中还有一些没有展开讲的后续出专题
主要有:如何扩容 resize?hash(key) 确定数组中位置的实现方式?
分类:
标签:
,
,
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)