当前位置: 首页 > news >正文

HashMap中的get,put方法源码解析(超详细)

 get

HashMap中的get方法,点进来发现 

  • 借助hash(key)方法算出键的哈希值。
  • 查找节点:调用getNode方法,利用前面得到的哈希值以及键本身去查找对应的节点。
  • 返回结果:要是找到了节点,就返回该节点的值;若没找到,就返回null

 

 先进行判断 如果tab是空 直接返回null

不是的话进行往下判断 先判断头结点的hash值以及key值是否和目标相等,相等返回头结点。

为什么要对头结点单独进行一次判断呢 因为头结点是最常被访问到的,单独对头节点进行检查,能够避免执行遍历链表或树的操作,从而显著提升查找的效率。

如果不是头结点,继续往下判断,判断是否是红黑树,是的话通过getTreeNode在树中查找,不是就在链表中遍历比对,找到了返回,

 while ((e = e.next) != null);

然后移动下一个节点。

红黑树的查找逻辑,判断是否有父节点,有的话root() 往上找根节点,没有的话这个节点就是根节点,再从根节点开始按红黑树逻辑进行查找。

这段代码是在二次哈希,目的是减少哈希冲突

如果键等于null那么直接返回0,否则将key的哈希值与无符号右移16位进行异或 

first = tab[(n - 1) & hash]) != null

 说明一下这里 n必须是2的幂次方。这样-1的二进制就是一堆1了,此时相当于取模,只不过位运算更高效

  • tab[(n - 1) & hash]:通过哈希值和数组长度的位运算,计算出键对应的数组索引,并获取该位置的头节点 first
  • first != null:判断该位置是否已存在元素(链表头节点或树节点)。

 put

 put和get其实差不多

 hash(key):调用hash方法计算键的哈希值 减少哈希冲突

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}
if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;

 判断tab是否为null或数组长度是否为0,进行resize()初始化

  • 首次初始化时,默认容量为16,负载因子为0.75,阈值(扩容临界点)为16 × 0.75 = 12
if (oldCap > 0) {if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)newThr = oldThr << 1; // double threshold}

主要分析这里就可以 这是是如果旧容量已经达到了最大值,那么就不在进行扩容,不然就直接左移一位,扩大两倍的容量。 

i = (n - 1) & hash:通过哈希值和数组长度的位运算确定桶位置。

  • p = tab[i]:获取桶位置的头节点。
  • p == null:若为空,说明该位置没有元素,直接插入新节点。

如果有值,那么进行判断hash与key还有equals 相等然后进行赋值替换。

桶有值且键不相等的话,继续往下面执行 判断是红黑树还是链表

else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}

 如果是链表的话,若遍历到尾节点,直接创建节点插入值,如果没有遍历到尾节点就发现有相等的键 那么直接标记,然后退出循环,在进行赋值覆盖。

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);

这一步是如果创建的节点使得大于等于8 那么就会由链表转成树

注意

 我们会发现如果tab小于64那么不会转成红黑树 而是先进行resize()进行扩容 然后如果超过了64 那那么才会转成红黑树

最后

  ++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;

++size size如果超过了阈值那么也触发 resize()进行扩容

http://www.xdnf.cn/news/1090909.html

相关文章:

  • PHP 基于模板动态生成 Word 文档:图片 + 表格数据填充全方案(PHPOffice 实战)
  • RabbitMQ 高级特性之延迟队列
  • SQL Server通过存储过程实现HTML页面生成
  • mac m1安装大模型工具vllm
  • 迁移Oracle SH 示例 schema 到 PostgreSQL
  • 双指针-15.三数之和-力扣(LeetCode)
  • 算法核心知识复习:排序算法对比 + 递归与递推深度解析(根据GESP四级题目总结)
  • Oracle 数据库升级踩坑:DBLink ORA-02019 问题解决思路
  • 使用 Docker 搭建 Rust Web 应用开发环境——AI教你学Docker
  • 工程改Mvvm
  • 一天一道Sql题(day04)
  • 基于lottie的微信小程序动画开发指南
  • CSS中的Element语法
  • 仓颉语言 1.0.0 升级指南:工具链适配、collection 操作重构与 Map 遍历删除避坑
  • ali linux 安装libreoffice
  • 《重构项目》基于Apollo架构设计的项目重构方案(多种地图、多阶段、多任务、状态机管理)
  • Context Engineering:从Prompt Engineering到上下文工程的演进
  • Ragas的Prompt Object
  • 微软 Bluetooth LE Explorer 实用工具的详细使用分析
  • JVM字节码加载与存储中的细节
  • 川翔云电脑:突破硬件极限,重构设计生产力范式
  • 【vim中替换】
  • 【自动驾驶】经典LSS算法解析——深度估计
  • BEV感知算法:自动驾驶的“上帝视角“革命
  • django 一个表中包括id和parentid,如何通过parentid找到全部父爷id
  • 免费扫描软件NAPS2:跨平台支持 旋转裁剪 + 多页合并,纸质文档变 PDF / 图片
  • 详解Kafka重平衡机制详解
  • Python(30)基于itertools生成器的量子计算模拟技术深度解析
  • 18-C#改变形参内容
  • 《设计模式之禅》笔记摘录 - 5.代理模式