[java进阶]——HashMap的底层实现原理和源码分析,另附几个高频面试题

news2024/7/2 4:00:04

🌈键盘敲烂,年薪30万🌈

目录

一、底层数据结构

二、底层原理及源码分析

2.1 继承关系

2.2 成员变量

2.3 构造方法

 2.4 重要的成员方法

     2.4.1 put()方法

三、高频面试题


一、底层数据结构

JDK8以后底层使用 数组+链表+红黑树的数据结构,当链表长度大于8并且数组长度大于64,链表自动转为红黑树

node与treenode

hashmap中每一个元素都是一个node对象或treenode对象,node是链表节点,treenode是红黑树节点。

node属性有hash值、key、value、next,treenode无非就是多了记录父节点,左右节点,节点颜色,prev(前驱)节点-即比当前节点小的最大节点。在红黑树中,每个节点都有一个指向前驱节点的指针,用于快速查找前驱节点。

二、底层原理及源码分析

2.1 继承关系

继承了AbstractMap抽象类,实现了Map接口

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable{}
2.2 成员变量
  1. transient Node<K,V>[] table: 用于存储HashMap中的键值对的数组,每个元素是一个链表节点,该节点存储了一个键值对。

  2. transient Set<Map.Entry<K,V>> entrySet: 用于存储HashMap中的键值对的一个set集合,其中每个元素都是一个Map.Entry类型的对象,该对象包含了一个键值对。

  3. transient int size: 用于记录HashMap中键值对的数量。

  4. int threshold: 表示HashMap中键值对的数量达到该值(容量*加载因子)时,会触发扩容操作

  5. final float loadFactor: 表示HashMap的加载因子,用于计算threshold。

  6. transient int modCount: 用于记录HashMap中结构发生变化的次数,用于快速失败机制。

  7. static final int MAXIMUM_CAPACITY = 1<<30: 表示HashMap中数组的最大容量,即2的30次方。

  8. static final float DEFAULT_LOAD_FACTOR: 表示HashMap的默认加载因子,为0.75

  9. static final int DEFAULT_INITIAL_CAPACITY = 1<<4: 表示HashMap的默认容量,为16。

  10. static final int TREEIFY_THRESHOLD = 8: 链表长度,当同时满足10和11时,链表转红黑树。

  11. static final int MIN_TREEIFY_CAPACITY = 64: Hashmap容量(数组长度),当同时满足10和时,链表转红黑树。

  12. static final int UNTREEIFY_THRESHOLD = 6: 当红黑树节点数量小于该值时,树会转化为链表。

  13. transient Set<K> keySet: 用于存储HashMap中的键,它是一个Set集合。

  14. transient Collection<V> values: 用于存储HashMap中的值,它是一个Collection集合。

  15. private static final long serialVersionUID = 362498820763181265L :保证不同版本的HashMap在序列化和反序列化时的版本一致性。

2.3 构造方法

4种构造方法演示

public class Test {
    public static void main(String[] args) {
        HashMap<String, String> hm1 = new HashMap();
        hm1.put("张翠山", "殷素素");
        //指定默认容量
        HashMap<String, String> hm2 = new HashMap<>(16);
        //指定默认容量和加载因子
        HashMap<String, String> hm3 = new HashMap<>(16, 0.7f);
        HashMap<String, String> hm4 = new HashMap<>(hm1);
        System.out.println(hm4);
        System.out.println(hm1);
    }
}

注意:

空参构造只是把默认加载因子的值赋给了加载因子这个变量,并没有创建table[]数组,此时的table数组是默认初始值,为null。

 2.4 重要的成员方法
     2.4.1 put()方法

调用putVal方法,方法参数讲解如下

返回值:返回被覆盖元素的值(value),如果没有覆盖,返回null

putval方法 原码讲解 重点来啦

首先明确一个问题:

hash是根据key值计算出来的,但是key值不同计算出来的hash值也可能相同,这叫hash碰撞,但是hash值不同key一定不同

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
		//tab用于保存Hashmap中数组的地址
		//tab是在栈上开辟的,访存速度快,而外面的table是在堆上开辟的,访问慢
        Node<K,V>[] tab; 
		//要添加的节点p
		Node<K,V> p; 
		int n, i;
		//数组没有创建或者数组长度为0进if
        if ((tab = table) == null || (n = tab.length) == 0){
			//如果是第一次添加元素,调用resize()方法,底层会创建一个默认长度为16,加载因子为0.75的数组
			//将tab的长度赋给变量n
            n = (tab = resize()).length;
		}

			//不是第一次添加元素
			//1.计算要添加元素在数组中的索引,也就是i
			//2.判断:数组中索引处是否为null
				//如果为null 直接添加一个新的节点到tab中
				//如果不为null,走else,此时p是数组中索引处的节点,默念三遍!!!
        if ((p = tab[i = (n - 1) & hash]) == null)
			//是null添加新节点
            tab[i] = newNode(hash, key, value, null);
        else {
			//不是null
            Node<K,V> e; K k;
			//1.判断要添加的hash值与数组中索引处的节点(p)的hash值是否相同
				//相同并且key也相同 覆盖元素
				//不相同走elseif 或 else
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
				//覆盖原数组中的元素
                e = p;
            else if (p instanceof TreeNode)
				//与p的hash不同 可添加该节点
				//是红黑树结构就添加红黑树的节点
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
				//与p的hash不同,以p为迭代对象循环找该链表的尾节点
                for (int binCount = 0; ; ++binCount) {
					//判断p是否是尾节点
                    if ((e = p.next) == null) {
						//是尾节点,在p的后面添加新的链表节点
                        p.next = newNode(hash, key, value, null);
						//添加完后判断是否转为红黑树结构
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
					//迭代的过程中有与添加元素重复的key,break跳出,注意此时e是与添加元素重复key的节点
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
					//迭代p
                    p = e;
                }
            }
			//如果e是空 添加新元素
			//如果e不是空 覆盖元素
            if (e != null) { // existing mapping for key
				//记录老的value
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
					//覆盖
                    e.value = value;
                afterNodeAccess(e);
				//返回老的value
                return oldValue;
            }
        }
		
        ++modCount; //不用管
		//向数组中添加元素需要判断是否达到了扩容时机 达到就扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

 再画个图便于你理解

三、高频面试题

问:

Hashmap的工作原理?

Hashmap中发生冲突怎么办?

Hashmap是如何扩容的

如果两个键的Hash值相同,你如何获取这两个Map.Entry对象

当发生冲突并且两节点的key相同时,是如何覆盖元素的

答:

      1. HashMap是一种基于哈希表实现的Map接口的键值对存储结构。工作原理可以简单概括为以下几个步骤:根据key值计算hashcode值,将键值对存储到表中,查找键值对。

      2. hash值相同叫做发生冲突,发生冲突之后依次比较待添加节点与链表或红黑树中的每一个节点的key值,如果key值相同,覆盖,返回要覆盖的元素的value。如果不相同并且到了尾节点,添加新节点。

      3. 当数组长度到达总长度的加载因子倍,扩容为原容量的两倍

      4. 当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后获取值对象。找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

      5.如果这个节点是数组中的,将新的节点替换原节点,如果是链表或红黑树中的节点,只修改原节点的value值。

📕总结

以上是hashmap的底层分析,希望对你有帮助

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

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

相关文章

贝锐花生壳内网穿透推出全新功能,远程业务连接更安全

贝锐旗下内网穿透兼动态域名解析品牌花生壳目前推出了全新的“访问控制”功能&#xff0c;可精确设置访问权限&#xff0c;充分保障信息安全&#xff0c;满足更多用户安全远程访问内网服务的需求。 通过这一功能&#xff0c;可实现指定时间、IP、地区等条件下才能远程访问映射的…

【C++】继承 ⑬ ( 虚继承原理 | 虚继承解决继承二义性问题 | 二义性产生的原因分析 )

文章目录 一、虚继承原理1、虚继承解决继承二义性问题2、二义性产生的原因分析3、虚继承原理 二、代码示例 - 虚继承原理1、完整代码示例2、执行结果 一、虚继承原理 1、虚继承解决继承二义性问题 继承的二义性 : 如果 一个 子类 ( 派生类 ) 继承多个 父类 ( 基类 ) , 这些父类…

【【萌新的FPGA学习之FIFO的介绍】】

萌新的FPGA学习之FIFO的介绍 FIFO first in first out FIFO 的作用更多的是 缓冲与缓存 或者FIFO 也常被用来使用为 FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器&#xff0c;其与普通存储器 RAM 的 区别在于 FIFO 没有外部读写地址线&#xff0c;使用起…

接口自动化测试方案

1、引言 1.1 文档版本 版本 作者 审批 备注 V1.0 XXXX 创建测试方案文档 1.2 项目情况 项目名称 XXX 项目版本 V1.0 项目经理 XX 测试人员 XXXXX&#xff0c;XXX 所属部门 XX 备注 1.3 文档目的 本文档主要用于指导XXX-YY项目常用接口自动化测试…

数据隐私保护与合规性:现代企业的数据安全策略

第一章&#xff1a;引言 在当今数字化时代&#xff0c;数据已经成为企业最宝贵的资源之一。然而&#xff0c;伴随着大规模数据收集和处理的增加&#xff0c;数据隐私保护和合规性问题也日益凸显。本文将深入探讨数据隐私保护和合规性对现代企业的重要性&#xff0c;并提供一些…

重大突破!国内首个ASIL D认证MCU在底盘域量产上车

中国本土车规级MCU再次实现了重要突破。 近日&#xff0c;芯驰科技的高性能车规MCU——E3搭载在明然科技悬架控制器&#xff08;CDC&#xff09;批量下线&#xff0c;并且成功在奇瑞瑞虎9、星途瑶光等车型上正式量产&#xff0c;成为了国内首个应用在主动悬架的车规控制芯片。…

行业追踪,2023-10-26

自动复盘 2023-10-26 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

干货很干:5个有效引流方法,让客户找上门

如何才能把用户引流到私域&#xff1f;是很多老板&#xff0c;店主&#xff0c;线上创业者的卡点&#xff0c;今天分享5个实用方法&#xff1a; ✅线下导流 ✅巧用搜索 ✅同行互推 ✅社群引流 ✅内容输出 所以引流不仅需要知道方法&#xff0c;还需要知道底层逻辑&#xff0c;也…

分布式事务(Seata)——Seata分布式事务XA模式、AT模式、TCC模式的介绍和对比 结合案例分析AT模式和XA模式【源码】

前言 事务(TRANSACTION)是一个不可分割的逻辑单元&#xff0c;包含了一组数据库操作命令&#xff0c;并且把所有的命令作为一个整体向系统提交&#xff0c;要么都执行、要么都不执行。 事务作为系统中必须考虑的问题&#xff0c;无论是在单体项目还是在分布式项目中都需要进行…

从零开始:开发知识付费小程序的入门指南

当下&#xff0c;知识付费小程序成为了一个独具潜力的领域。本篇文章将为您提供一份从零开始的知识付费小程序开发入门指南&#xff0c;让您能够进入这个领域并开始赚取您的专业知识。 第一步&#xff1a;什么是知识付费小程序&#xff1f; 知识付费小程序是一种基于微信小程…

数据创建与数据管理

原文&#xff1a;Dataset Creation and Curation Introduction to Data-Centric AI 为监督学习创建数据集需要样本以及样本的标签。课程专注于分类任务&#xff0c;但是把这些原理运用到其它监督学习的任务也是可以的。 数据收集 寻找训练数据时的关键问题&#xff1a; 1.…

c#学习相关系列之构造函数

目录 一、构造函数的作用 二、构造函数的特征 三、三种构造函数介绍 1、实例构造函数 2、静态构造函数 3、私有构造函数 一、构造函数的作用 构造函数用来创建对象&#xff0c;并且可以在构造函数中对此对象进行初始化。构造函数具有与类相同的名称&#xff0c;它通常用来…

每日一练 | 网络工程师软考真题Day45

阅读以下说明&#xff0c;答复以下【问题1】至【问题4】 【说明】 某公司有1个总部和2个分部&#xff0c;各个部门都有自己的局域网。该公司申请了4个C类IP地址块 202.114.10.0/24~202.114.13.0/24。公司各部门通过帧中继网络进行互联&#xff0c;网络拓扑结构如图1-1所示。 【…

如何在线去除图片上的水印?一分钟教你一键去除

想要去除图片上的水印&#xff1f;不妨试试在线图片水印去除工具&#xff01;在个人生活或工作中&#xff0c;我们常常需要使用他人的图片&#xff0c;然而图片上的水印却常常成为阻碍&#xff0c;若你正为此烦恼&#xff0c;那么在线图片去水印工具将成为你的救星&#xff0c;…

禁止使用U盘的方法

禁止使用U盘的方法 说到保护电脑数据安全&#xff0c;无论是个人还是企业&#xff0c;这都是一个很重要的问题&#xff0c;大家都会想到设置电脑开机密码&#xff0c;但这还远远达不到我们的要求&#xff0c;有很多种方法可以跳过开机验证&#xff0c;直接进入电脑。所以我们还…

代码随想录Day29 贪心04 LeetCode T860 柠檬水找零 T406 根据身高重建队列 T452 用最少得箭引爆气球

LeetCode T860 柠檬水找零 题目链接:860. 柠檬水找零 - 力扣&#xff08;LeetCode&#xff09; 题目思路: 这道题我们只要顺序按照数组判断是否能有钱找零即可,我们定义三个变量来记录每张钞票目前的数量,其中我们知道给10元得找5元,给二十元得找515元,而15元的组合有10元5元和…

算法通过村第十七关-贪心|青铜笔记|贪心也很简单呕

文章目录 前言难以解释的贪心算法贪心问题发放饼干柠檬水找零分发糖果 总结 前言 提示&#xff1a;我像接纳变甜的果实一般迎接此时。 --朱塞培翁加雷蒂《享受》 贪心的思想很难用理论去解释&#xff0c;这里我们通过案例感受下&#xff0c;怎么思考贪心的问题。 难以解释的贪心…

threejs(5)-详解灯光与阴影

一、Gsap动画库基本使用与原理 npm 地址&#xff1a;https://snyk.io/advisor/npm-package/gsap import * as THREE from "three"; // 导入轨道控制器 import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; // 导入动画库 import…

设备巡检怎么规范流程?如何做好后勤管理工作?

后勤巡检对于企业和高校来说&#xff0c;就像一台电脑上的安全防护软件&#xff0c;它会定期或不定期地扫描和检查系统&#xff0c;找出存在的问题&#xff0c;例如垃圾文件和病毒风险。巡检的目的是为了检查公司和高校的各项设施、设备和环境等&#xff0c;以发现潜在的环境安…

React之服务端渲染

一、是什么 在SSR中 (opens new window)&#xff0c;我们了解到Server-Side Rendering &#xff0c;简称SSR&#xff0c;意为服务端渲染 指由服务侧完成页面的 HTML 结构拼接的页面处理技术&#xff0c;发送到浏览器&#xff0c;然后为其绑定状态与事件&#xff0c;成为完全可…