【Java集合篇】HashMap 在 get 和 put 时经过哪些步骤

在这里插入图片描述

HashMap在get和put时经过哪些步骤?

  • ✔️ 典型解析
  • ✔️get方法
  • ✔️put方法
  • ✔️ 拓展知识仓
    • ✔️ HashMap如何定位key
    • ✔️ HashMap定位tablelndex的骚操作作
    • ✔️HashMap的key为null时,没有hashCode是如何存储的?
    • ✔️ HashMap的value可以为null吗? 有什么优缺点讷?


✔️ 典型解析


对于HashMap来说,底层是基于散列算法实现,散列算法分为散列再探测拉链式HashMap 则使用了拉链式的散列算法,即采用数组+链表/红黑树来解决hash冲突,数组是HashMap的主体,链表主要用来解决哈希冲突。这个数组是Entry类型,它是HashMap的内部类,每一个Entry包含一个keyvalue键值对。


✔️get方法


对于get方法来说,会先查找桶,如果hash值相同并且key值相同,则返回该node节点,如果不同,则当node.next!=null时,判断是红黑树还是链表,之后根据相应方法进行查找。


直接看一个Demo吧,帮助理解。


import java.util.HashMap;  
import java.util.Map;  // 定义一个HashMap类,该类继承了HashMap类 
public class ComplexHashMap<K, V> extends HashMap<K, V> {  // 定义默认的初始容量和加载因子  private static final int DEFAULT_INITIAL_CAPACITY = 16;private static final float DEFAULT_LOAD_FACTOR = 0.75f;// 定义树化操作的阈值   private static final int MAX_TREEIFY_THRESHOLD = 8;// 定义存储红黑树根节点的数组    private Entry<K, V>[] treeRoots;// 定义树化操作的阈值  private int treeifyThreshold;  public ComplexHashMap() {  this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);  }  public ComplexHashMap(int initialCapacity, float loadFactor) {// 调用父类的构造函数进行初始化,传入初始容量和加载因子参数   super(initialCapacity, loadFactor);  treeRoots = new Entry[DEFAULT_INITIAL_CAPACITY];// 计算树化操作的阈值,该值等于初始容量乘以加载因子    treeifyThreshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);  }  // 重写父类的rehash方法,用于在需要时重新哈希键值对并可能进行树化操作  @Override  protected void rehash(int newCapacity) {  super.rehash(newCapacity);  if (newCapacity > treeifyThreshold && size > MAX_TREEIFY_THRESHOLD) {  for (Entry<K, V> entry : this.entrySet()) {  K key = entry.getKey();  if (containsKey(key)) {  put(key, entry.getValue());  }  }  treeify();  }  }  // 定义一个私有方法treeify用于将map中的元素根据键进行排序并重新组织成红黑树结构以提升查询效率。private void treeify() {  Entry<K, V>[] newTreeRoots = new Entry[size()];  for (Entry<K, V> entry : this.entrySet()) {  K key = entry.getKey();  int index = Math.abs(key.hashCode()) % newTreeRoots.length;  if (newTreeRoots[index] == null) {  newTreeRoots[index] = new Entry<>(key, entry.getValue());  } else {  Entry<K, V> current = newTreeRoots[index];  while (true) {  int comparison = current.key.compareTo(key);  if (comparison == 0) {  current.value = entry.getValue(); // replace value if different key with same hashcode found  break;  } else if (comparison < 0) {  if (current.left == null) {  current.left = new Entry<>(key, entry.getValue());  break;  } else {  current = current.left;  }  } else { // comparison > 0  if (current.right == null) {  current.right = new Entry<>(key, entry.getValue());  break;  } else {  current = current.right;  }  }  } }   } treeRoots = newTreeRoots; }    
} 

✔️put方法


对于put方法来说,一般经过以下几步:


1 . 如果数组没有被初始化,先初始化数组


2 . 首先通过定位到要 putkey 在哪个桶中,如果该桶中没有元素,则将该要 putentry 放置在该桶中


3 . 如果该桶中已经有元素,则遍历该桶所属的链表:
    a . 如果该链表已经树化,则执行红黑树的插入流程


    b . 如果仍然是链表,则执行链表的插入流程,如果插入后链表的长度大于等于8,并目桶数组的容量大于等于64,则执行链表的树化流程


    c . 注意: 在上面的步骤中,如果元素和要put的元素相同,则直接替换


4 . 校验是新增 KV 还是替换老的KV,如果是后者,则设置 callback 扩展(LinkedHashMap LRU 即通过此实现)


5 . 校验 ++size 是否超过 threshold ,如果超过,则执行扩容流程 (见下会分解~)


读完文字,我们借助于代码片段捋一捋:


import java.util.HashMap;  
import java.util.Map;  /**
*   @author xinbaobaba
*   一个简单的Demo,帮助理解HashMap在put操作时的基本步骤
*/  
public class HashMapPutExample {  public static void main(String[] args) {  // 创建一个新的HashMap对象  Map<String, Integer> map = new HashMap<>();  // 添加键值对到HashMap中  map.put("Alice", 25);  map.put("Bob", 30);  map.put("Charlie", 35);  // 输出原始HashMap的状态  System.out.println("Before modification:");  for (Map.Entry<String, Integer> entry : map.entrySet()) {  System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());  }  // 修改一个键对应的值  map.put("Alice", 30);  // 输出修改后的HashMap的状态  System.out.println("\nAfter modification:");  for (Map.Entry<String, Integer> entry : map.entrySet()) {  System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());  }  }  
}

输出结果如下:


Before modification:  
Key: Alice, Value: 25  
Key: Bob, Value: 30  
Key: Charlie, Value: 35  After modification:  
Key: Alice, Value: 30  
Key: Bob, Value: 30  
Key: Charlie, Value: 35

✔️ 拓展知识仓


✔️ HashMap如何定位key


先通过 (table.length - 1) & (key.hashCode ^ (key.hashCode >> 16)) 定位到 key 位于哪个table 中,然后再通过key.equals(rowKey)来判断两个key是否相同,综上,是先通过hashCodeequals 来定位 KEY 的。


源码如下:


static final int hash(Object key) {int h;return (key == null) ?  0 : (h = key.hashCode()) ^ (h >>> 16);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {// ...省略if ((p = tab[i = (n - 1) & hash]) == null) {tab[i] = newNode(hash, key, value, null);} else {Node<K,V> e; K k;// 这里会通过equals判断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);}// ...省略return null:}
}

所以,在使用 HashMap 的时候,尽量用StringEnum 等已经实现过 hashCodeequals方法的官方库类,如果一定要自己的类,就一定要实现 hashCodeequals 方法。


✔️ HashMap定位tablelndex的骚操作作


通过源码发现,hashMap 定位 tablelndex 的时候,是通过 (table.length - 1)& (key.hashCode ^ (key.hashCode >> 16)) ,而不是常规的key.hashCode % (table.length)呢?


1 . 为什么是用 & 而不是用 % :

:因为 & 是基于内存的二进制直接运算,比转成十进制的取模快的多。以下运算等价: X % 2^n = X & (2^n - 1) 。这也是 hashMap 每次扩容都要到2^n的原因之一

2 . 为什么用 key.hash ^ (key.hash >> 16)而不是用key.hash:

:这是因为增加了扰动计算,使得 hash分布的尽可能均匀。因为 hashCodeint 类型,虽然能映射40亿左右的空间,但是,HashMaptable.length毕竟不可能有那么大,所以为了使 hash%table.length 之后,分布的尽可能均匀,就需要对实例的hashCode的值进行扰动,说白了,就是将hashCode的高16和低16位,进行异或使得hashCode的值更加分散一点


✔️HashMap的key为null时,没有hashCode是如何存储的?


HashMap 对 key=null 的 case 做了特殊的处理,key值为 null 的 kv 对,总是会放在数组的第一个元素中,如下源码所示:


private V putForNulKey(V value) {for (Entry<K,V> e = table[0]; e != null; e = e.next)  {if (e.key == null) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(0,null, value, 0);return null;
}private V getForNul1Key()  {for (Entry<K,V> e = table[0]; e != null; e = e.next)  {if (e.key == null)return e.value;}return null;
}

✔️ HashMap的value可以为null吗? 有什么优缺点讷?


HashMap的kevvalue都可以为null,优点很明显,不会因为调用者的粗心操作就抛出NPE这种RuntimeException,但是缺点也很隐蔽,就像下面的代码一样:


//调用远程RPC方法,获取map
Map<StringObject> map = remoteMethod.queryMap();
//如果包含对应key,则进行业务处理
if(map.contains(KEY)) {String value = (string)map.get(KEY);System.out.printIn(value );
}

虽然map.contains(key),但是 map.get(key)==null,就会导致后面的业务逻辑出现NPE问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/599728.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

群太多,有什么办法可以定时、批量、标签分类群发的吗?

你或许会遇到微信群组过多而导致管理困难等问题吗&#xff1f;对于如何高效地管理众多微信群组&#xff0c;实现分类管理同样具有不可忽视的重要性。 实际上&#xff0c;对此问题小编收到了部分朋友的疑问&#xff1a;“既然可以为微信好友添加标签&#xff0c;那为何不能为微…

数据库定义和操作语句的重要语法

数据库&#x1f4ca;的10种语法 数据查询语句 select : 用于从数据库中检索数据。 select column1, column2,.... from table_name where condition; select distinct : 用于从数据库中检索唯一的数据值。 select DISTINCT column1, clumn2,... from table_name; 工作原理…

torch.meshgrid和np.meshgrid的区别

numpy中meshgrid&#xff1a; 把数组a当作一行&#xff0c;再根据数组b的长度扩充行。 把数组b当作一列&#xff0c;再根据数组a的长度扩充列。 torch中meshgrid&#xff1a; 把数组a当作一列&#xff0c;再根据数组b的长度扩充列。 把数组b当作一行&#xff0c;再根据数组a的…

CAVER: Cross-Modal View-Mixed Transformer for Bi-Modal Salient Object Detection

目录 一、论文阅读笔记&#xff1a; 1、摘要&#xff1a; 2、主要贡献点&#xff1a; 3、方法&#xff1a; 3.1 网络的总体框架图&#xff1a; 3.2 Transformer-based Information Propagation Path (TIPP) 3.3 Intra-Modal/Cross-Scale Self-Attention (IMSA/CSSA) Q1…

onxxruntime使用cuda以及tensorrt进行加速

1、版本匹配 版本需要匹配&#xff0c;不然运行会报错 2、onnxruntime之tensorrt加速 方式一&#xff1a; OrtTensorRTProviderOptions trt_options{}; trt_options.trt_max_workspace_size 2147483648; trt_options.trt_max_partition_iterations 10; trt_options.trt_m…

kali-Linux安装ARL灯塔教程以及timeout of 20000ms exceeded 的解决方法

FLAG&#xff1a;别和妈妈诉苦&#xff0c;她帮不上&#xff0c;也睡不着。 专研方向: docker&#xff0c;ARL资产灯塔系统 每日emo&#xff1a;天冷了&#xff0c;你还在坚持吗&#xff1f; 欢迎各位与我这个菜鸟交流学习 kali安装ARL灯塔教程 1.安装docker环境&#xff0c;…

【MySQL】orderby/groupby出现Using filesort根因分析及优化

序 在日常的数据库运维中&#xff0c;我们可能会遇到一些看似难以理解的现象。比如两个SQL查询语句&#xff0c;仅仅在ORDER BY子句上略有不同&#xff0c;却造成了性能的天壤之别——一个飞速完成&#xff0c;一个则让数据库崩溃。今天就让我们围绕这个问题&#xff0c;深入剖…

LeetCode每日一题 | 1944. 队列中可以看到的人数

文章目录 队列中可以看到的人数题目描述问题分析程序代码&#xff08;Golang 版本&#xff09; 队列中可以看到的人数 题目描述 原题链接 有 n 个人排成一个队列&#xff0c;从左到右 编号为 0 到 n - 1 。给你以一个整数数组 heights &#xff0c;每个整数 互不相同&#xff…

Mysql的安装配置教程(非常详细)从零基础入门到精通,看完这一篇就够了

首先简单概述分为几个步骤&#xff1a; 一、下载Mysql 二、安装Mysql 三、验证Mysql安装是否成功 四、 配置环境变量 五、验证配置环境变量是否成功 一、下载Mysql 要在Windows或Mac上安装MySQL&#xff0c;首先从MySQL官方网站下载最新的MySQL Community Server版本&am…

QCharView使用

QChart是 QGraphicsWidget的子类。 QCharView是QGraphicsView的子类 QCharView概念:title、系列、图标Chart、视图 说明: 需要添加Qt组件charts 在使用QChart或者QChartView之前需要添加宏定义QT_CHARTS_USE_NAMESPACE &#xff08;其实是使用了命名空间&#xff09;&#xff…

Baumer工业相机堡盟工业相机如何联合NEOAPI SDK和OpenCV实现相机图像转换为Mat图像格式(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK实现相机掉线自动重连&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的图像转换为OpenCV的Mat图像的技术背景在NEOAPI SDK里实现相机图像转换为Mat图像格式联合OpenCV实现相机图像转换为Mat图像格式测试演示图 工业相机…

SQL-取连续日期或者数值

从LVV_START_TIMEKEY开始&#xff0c;取LVV_END_TIMEKEY - LVV_START_TIMEKEY 1个连续的日期&#xff1a; SELECTTO_DATE (:LVV_START_TIMEKEY,YYYYMMDD) ROWNUM-1 AS wwm FROMDUAL CONNECT BYROWNUM < LAST_DAY (TO_DATE (:LVV_END_TIMEKEY,YYYYMMDD)) - TO_DATE (:LVV_…

大容量交流中间继电器RXMK1 RK225052 220VAC 板前接线带座 JOSEF约瑟

系列型号&#xff1a; RXMK1 RK 255 049大容量交流中间继电器 RXMK1 RK 255 050大容量交流中间继电器 RXMK1 RK 255 051大容量交流中间继电器 RXMK1 RK 255 052大容量交流中间继电器 RXMK1 RK 255 053大容量交流中间继电器 RXMK1大容量交流中间继电器 一、 用途及工作原理 RX…

【深入理解 ByteBuf 之一】 release() 的必要性

引言 开个新坑 【深入理解 ByteBuf】 至于为什么&#xff0c;本篇就是原因 我大概会花一个较长的时间来剖析 Netty 对于 ByteBuf 的实现&#xff0c;对象池的设计&#xff0c;从分配到释放重用&#xff0c;希望可以借此学习理解对象池的设计思想&#xff0c;以及搞清楚&#x…

XTU OJ 1525瓷片

题意 给定一个2n的地面&#xff0c;用11和1*2的瓷片铺满&#xff0c;问有多少种方案 数据范围 n<30 输入 3 1 2 30 输出 2 7 1084493574452273 代码 #include<stdio.h>int main() {int t;scanf("%d",&t);long long a[40];a[0]1,a[1]2,a[2]7;fo…

Vue 3.4 发布

本文为翻译 原文地址&#xff1a;Announcing Vue 3.4 | The Vue Point — Vue 3.4 发布公告 |Vue 点 (vuejs.org) 今天&#xff0c;我们很高兴地宣布 Vue 3.4 “&#x1f3c0;灌篮高手”的发布&#xff01; 此版本包括一些实质性的内部改进 - 最引人注目的是重写的模板解析器&…

晶圆代工降价竞争进入白热化,降幅最高15% | 百能云芯

随着半导体产业的不确定性和市况回落&#xff0c;晶圆代工市场再次掀起波澜&#xff0c;“降价大军”再添猛将。 据综合媒体报道&#xff0c;传三星计划在2024年第一季度调降晶圆代工报价&#xff0c;提供5%至15%的折扣&#xff0c;并表示愿意进一步协商。 台积电根据客户的投产…

一、Vue3组合式基础[ref、reactive]

一、ref 解释&#xff1a;ref是Vue3通过ES6的Proxy实现的响应式数据&#xff0c;其与基本的js类型不同&#xff0c;其为响应式数据&#xff0c;值得注意的是&#xff0c;reactive可以算是ref的子集&#xff0c;ref一般用来处理js的基本数据类型如整型、字符型等等(也可以用来处…

Java集合框架深度解析-ArrayList

Java的集合框架提供了一组实现常用数据结构的类和接口。理解集合框架对于Java程序员来说至关重要&#xff0c;因为它们在日常编程中广泛应用。 为什么需要集合框架&#xff1f; 在编程中&#xff0c;我们经常需要存储和操作一组对象。集合框架提供了用于表示和操作对象组的通…

从 YOLOv1 到 YOLO-NAS 的所有 YOLO 模型:论文解析

在计算机视觉的浩瀚领域&#xff0c;有一支耀眼的明星&#xff0c;她的名字传颂着革新与突破的传奇——YOLO&#xff08;You Only Look Once&#xff09;。回溯时光&#xff0c;走进这个引人注目的名字背后&#xff0c;我们仿佛穿越进一幅画卷&#xff0c;一幅展现创新魅力与技…