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

HashMap的Get(),Put()源码解析

1、什么是 HashMap?

HashMap 是 Java 中用于存储键值对(Key-Value)的集合类,它实现了 Map 接口。其核心特点是:

无序性:不保证元素的存储顺序,也不保证顺序恒定不变。

唯一性:键(Key)不能重复,若插入重复键会覆盖原有值。

允许 null:允许一个 null 键和任意数量的 null 值。

非线程安全:相比 HashTableHashMap 不支持同步,性能更高。

2. 核心数据结构:哈希表(HashTable)

HashMap 的底层是一个 哈希表,本质是一个 数组 + 链表 / 红黑树 的复合结构:
  • 数组:也称为 “哈希桶”(Bucket Array),每个位置称为一个 “桶”(Bucket)。
  • 链表 / 红黑树:当多个键通过哈希函数映射到同一个桶时,这些键值对会以链表或树的形式存储。

3、get()源码及分析

get()方法是 HashMap 中用于获取指定键对应值的核心方法.
源码如下
public V get(Object key) {Node<K,V> e;// 调用getNode方法获取节点,若节点存在则返回其值,否则返回nullreturn (e = getNode(hash(key), key)) == null ? null : e.value;
}
/*** 实现Map.get及其相关方法的核心逻辑* * @param hash 键的哈希值(通过hash(key)计算得到)* @param key 要查找的键* @return 对应的节点,如果不存在则返回null*/
final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// 检查哈希表是否存在且不为空,以及对应的桶是否有节点//这里tab[(n - 1) & hash是根据键的哈希值 hash,计算其在哈希表数组 tab 中的索引位置//当 n 是 2 的幂时,(n - 1) & hash 等价于 hash % nif ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 首先检查桶中的第一个节点if (first.hash == hash &&  // 哈希值相等((k = first.key) == key || (key != null && key.equals(k))))  // 键相等(引用相等或equals为true)return first;  // 找到匹配的键,返回第一个节点// 如果第一个节点不匹配且有后续节点if ((e = first.next) != null) {//当链表长度超过 8 且数组长度超过 64 时,链表会转换为红黑树// 如果是红黑树节点,调用红黑树的查找方法if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 否则遍历链表do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;  // 找到匹配的键,返回对应节点} while ((e = e.next) != null);}}// 未找到匹配的键,返回nullreturn null;
}
get方法流程
  1. 计算键的哈希值和数组索引。
  2. 检查对应桶的首节点:
  • 若首节点的键匹配,直接返回。
  • 若首节点是树节点,调用红黑树的查找方法。
  • 否则遍历链表查找。
注意!
  • 通过hash(key)方法计算键的哈希值,该方法会将键的原始 hashCode 与其高 16 位进行异或操作,以减少哈希冲突。
  • 使用(n - 1) & hash计算数组索引,其中n是数组长度(始终为 2 的幂),这种方式等价于取模运算但效率更高。

4、put()源码及分析

put() 是 HashMap 最核心的方法之一,用于存储键值对。
源码如下:
public V put(K key, V value) {// 调用 putVal 方法,传入键的哈希值、键、值等参数return putVal(hash(key), key, value, false, true);
}/*** 实现 Map.put 及其相关方法的核心逻辑* * @param hash 键的哈希值* @param key 键* @param value 值* @param onlyIfAbsent 如果为 true,则不覆盖已存在的值* @param evict 如果为 false,表示处于创建模式(用于 LinkedHashMap)* @return 旧值(如果存在),否则返回 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;// 检查数组是否为空或长度为0,若是则初始化数组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);// 链表长度达到树化值(默认8),转换为红黑树if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);break;}// 检查链表中的节点是否与键匹配if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;  // 找到匹配的键,跳出循环p = e;  // 移动到下一个节点}}// 步骤6:处理键已存在的情况if (e != null) {  // 键已存在V oldValue = e.value;// 根据 onlyIfAbsent 参数决定是否更新值if (!onlyIfAbsent || oldValue == null)e.value = value;// LinkedHashMap 的回调方法(HashMap 中为空实现)afterNodeAccess(e);return oldValue;  // 返回旧值}}// 步骤7:记录修改次数,检查是否需要扩容++modCount;if (++size > threshold)//扩容resize();// LinkedHashMap 的回调方法(HashMap 中为空实现)afterNodeInsertion(evict);return null;  // 键不存在,返回 null
}
put方法流程
  1. 计算键的哈希值和数组索引。
  2. 如果对应桶为空,直接插入新节点。
  3. 如果桶中已有节点:
  • 若首节点的键匹配,覆盖其值。
  • 若首节点是树节点,调用红黑树的插入方法。
  • 否则遍历链表,找到相同键则覆盖,未找到则插入新节点(链表长度≥8 时树化)。

     4.插入后检查是否需要扩容。

注意!

        首次插入时,数组默认长度为 16。扩容条件,键值对数量超过阈值(容量 × 负载因子)。扩容步骤,数组长度翻倍(如 16 → 32)。

http://www.dtcms.com/a/271452.html

相关文章:

  • CTFHub————Web{信息泄露[备份文件下载(网站源码、bak文件)]}
  • 微服务架构中数据一致性保证机制深度解析
  • [Backlog] 核心协调器 | 终端用户界面(TUI)实现 | 多分支任务冲突解决 | 测试验证体系
  • vue2中使用xgplayer播放流视频
  • 方差、协方差和协方差矩阵
  • 软件编码规范、运行时错误、安全漏洞与缺陷:解析及库博的检测能力
  • Dify 文本语意识别与自动补全工作流
  • VD6052系列 30V 200mA的低功耗稳压器芯片 适用12V/24V供电MCU
  • 腾讯0708面试手撕题:严格递增数字分割
  • 17-C#封装,继承,多态与重载
  • git配置密钥
  • MTK-系统设置Settings 开机累计时长源码分析
  • AI芯片产品经理:算力革命的架构师
  • Mysql底层专题(七)MVCC多版本并发控制机制
  • STM32-定时器输入捕获
  • 高级LoRA:面向垂直领域LLM的实战微调指南——LoRA合并、续训、堆叠,Checkpoint管理
  • 佰力博PEAI压电分析仪-精准测量压电材料d33系数
  • RAG实战指南 Day 11:文本分块策略与最佳实践
  • HCIP(综合实验)
  • 腾讯位置商业授权未来驾车ETA(批量)
  • Fluent许可配置常见问题
  • ARM汇编编程(AArch64架构)课程 - 第8章:控制流与循环
  • 数字化管理新趋势:权限分级看板如何筑牢安全防线
  • 【Java】【力扣】【字节高频】3.无重复字符的最长字串
  • HTTP API 身份认证
  • 【Qt】Qt QML json处理
  • 微信获取access_token授权的两种不同情况
  • 零成本实现文本转语音
  • python网络爬虫笔记21:天地图解析服务调用教程
  • 正点原子学习 用户权限管理