服务端开发之Java备战秋招面试篇2-HashMap底层原理篇

news2024/12/26 23:03:51

现在Java应届生和实习生就业基本上必问HashMap的底层原理和扩容机制等,可以说是十分常见的面试题了,今天我们来好好整理一下这些知识,为后面的秋招做足准备,加油吧,少年。

目录

1、HashMap集合介绍

 2、HashMap的存储过程

3、HashMap的成员变量

4、put()方法底层实现

 5、将链表转换为红黑树

 6、HashMap的扩容机制与原理

 7、remove()方法底层原理

 8、get()方法底层原理

 9、ConcurrentHashMap底层原理

10、算法题:最长公共子序列


1、HashMap集合介绍

基于Hash表的Map接口的实现,此实现提供了所有可选的映射操作,并且允许使用null键和null值,不保证存储顺序。

JDK1.8之前是数组+链表的形式,数组是主体,链表用来解决hash冲突,jdk1.8之后引入红黑树提高查询效率(链表长度大于8时候,数组长度大于64,链表转换成红黑树)。

 2、HashMap的存储过程

我们看一下HashMap的存储过程,每次根据key的值进行进行相应的hash算法计算出hash值,对应数组下标的位置,如果对应位置没有元素,则直接存取,对应位置有链表,则遍历链表,用equals比对,若为true,则覆盖原来的键值对,否则在链表后划出一个节点存储数据。

在不断添加数据的过程中,可能会涉及到扩容,当超出临界值(且存放的位置非空时)扩容,默认的扩容方式:原始容量为16,扩容为原来的两倍,将原有的数据复制过来。

注意:使用resize()进行扩容,size超过临界数目就需要扩容,临界数目=容量*加载因子。

 

3、HashMap的成员变量

HashMap的初始容量为什么是2的n次幂呢?其实就是为了减少hash碰撞,因为根据key计算hash值,hash碰撞,就要检索同一个链表,这样存取性能就比较低,尽量减少碰撞,使得数据分布均匀。

我们做个总结吧,其实可以发现数组长度不是2的n次幂的情况下,会增大hash冲突,导致很多数组位置上一直不会插入数据,这样就会浪费空间,而链表的长度会越来越长。为了提升性能,通过位运算代替取余的方式确定位置。

 

0.75是一个时间和空间的折衷,再看一下这个负载因子,默认0.75,初始容量默认16,当存储数据大于初始容量*负载因子时,需要对hashMap进行扩容。链表中的值超过8的时候会转变为红黑树,节点少的时候不应该用红黑树,因为红黑树插入和删除元素要通过变色和旋转保持平衡,节点少的时候反而得不偿失。

加载因子默认是0.75,当元素达到HashMap的75%时,就需要扩容,扩容相对来说耗费性能,应尽量减少扩容次数,通过创建HashMap集合对象指定初始容量来尽量避免扩容次数。

我们再看一个面试题,为什么加载因子设置0.75,初始化临界值为12呢。因为如果加载因子过大,会导致链表比较长,元素查找的性能比较低,如果加载因子过小,那么数组稀疏,扩容会导致空间的浪费。

扩容的计算公式如下,每次大于容量的75%就扩容,每次扩容变成之前的两倍,如下:

4、put()方法底层实现

我们先大概看一下put()方法底层实现的基本原理,首先通过hash算法计算出hash值,映射到对应的数组空间,若没有发生hash碰撞 ,则直接插入,否则遍历链表或红黑树,用equals去比对,如果为true则覆盖原来的键值对,否则直接插入链表或红黑树中,当数组容量大于阈值的时候,需要进行扩容。

我们先看一下这个计算hash值得源码,是先计算hashCode()值,在与这个hash值的无符号右移进行异或操作,即可计算出hash值,这样可以减少hash碰撞。即映射到数组的位置更好,即hashMap得数组下标。

我们调用put()方法底层调用得是putVal()方法,在putVal()方法中,使用到了之前计算出来得hash值,若没有发生hash碰撞 ,则直接插入,否则遍历链表或红黑树,用equals去比对,如果为true则覆盖原来的键值对,否则直接插入链表或红黑树中,当数组容量大于阈值的时候,需要进行扩容。

 5、将链表转换为红黑树

如果链表的长度大于8且数组的长度大于64则将链表转换成为红黑树,根据hash表中的元素决定是要扩容还是要变成红黑树,如果是变成红黑树,是需要创建相同个数的树形节点,然后复制内容,建立连接,让数组中的元素指向新创建的树的根节点,将链表的内容替换为树。

 6、HashMap的扩容机制与原理

HashMap的扩容方法是resize(),我们先看一下扩容机制,即什么时候需要扩容,当元素个数大于数组长度 *0.75时候会选择扩容,每次扩容为原来的两倍。一般来说,扩容是非常消耗性能的。

下面看一下扩容的原理,在进行hashMap扩充的时候,不需要重新计算Hash值,只需要看原来的hash值新增的bit是1还是0就可以了,是0就是原来的位置,是1就是原位置+旧容量。

我们看一下原来16位的扩充为32的一个示意图,如果计算出高位是0,比如蓝色的15,就存储在原来的位置,比如绿色的15,就存储在原位置+旧容量=31的位置,如下:

这种rehash的方式,省去了重新计算hash的时间,由于新增的1和0bit是随机的,所以在扩容的过程中保证了每个桶中的节点树少于之前的,减少hash冲突。

 7、remove()方法底层原理

remove()方法是根据key通过hash算法找到数组下标,如果没有元素返回为空,如果是链表就遍历链表后删除,如果是红黑树就遍历红黑树后删除。链表长度大于8转树,树的节点小于6转链表,根据统计学泊松分布得到。

 8、get()方法底层原理

这个get()方法的底层原理和remove()方法很像,也是通过hash算法计算key的映射位置,若该位置就是key,则直接返回,若是红黑树或者链接,则应该遍历红黑树或链表,红黑树的查找性能类似于二分查找,效率更高,链表的话,就是顺序遍历,性能较低。

 9、ConcurrentHashMap底层原理

ConcurrentHashMap是在HashMap的基础之上对Node节点进行加锁的方式保证了线程的安全性,在JDK1.7中锁的粒度是segment片段,在JDK1.8中锁的粒度是数组中的某个节点,性能提升了,引入的红黑树降低检索的复杂度。锁的实现:JDK7的锁是segment,是基于ReentronLock实现的,包含多个HashEntry;而JDK8 降低了锁的粒度,采用 table 数组元素作为锁,从而实现对每行数据进行加锁,进一步减少并发冲突的概率,并使用 synchronized 来代替 ReentrantLock,因为在低粒度的加锁方式中,synchronized 并不比 ReentrantLock 差,在粗粒度加锁中ReentrantLock 可以通过 Condition 来控制各个低粒度的边界,更加的灵活。

10、算法题:最长公共子序列

题目链接:力扣

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        //最长公共子序列
        int n = text1.length(), m = text2.length() ;
        int [][] dp = new int[n+1][m+1] ;
        for(int i=1; i<=n; i++){
            for(int j=1; j<=m; j++){
                if(text1.charAt(i-1) == text2.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1] + 1 ;
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]) ;
                }
            }
        }
        return dp[n][m] ;
  
    }
}

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

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

相关文章

S2-001漏洞分析

首发于个人博客&#xff1a;https://bthoughts.top/posts/S2-001漏洞分析/ 一、简介 1.1 Struts2 Struts2是流行和成熟的基于MVC设计模式的Web应用程序框架。 Struts2不只是Struts1下一个版本&#xff0c;它是一个完全重写的Struts架构。 1.2 S2-001 Remote code exploit o…

【保姆级】手把手捋动态代理流程(JDK+Cglib超详细源码分析)

简介动态代理&#xff0c;通俗点说就是&#xff1a;无需声明式的创建java代理类&#xff0c;而是在运行过程中生成"虚拟"的代理类&#xff0c;被ClassLoader加载。 从而避免了静态代理那样需要声明大量的代理类。上面的简介中提到了两个关键的名词&#xff1a;“静态…

C语言进阶(九)—— 函数指针和回调函数、预处理、动态库和静态库的使用、递归函数

1. 函数指针1.1 函数类型通过什么来区分两个不同的函数&#xff1f;一个函数在编译时被分配一个入口地址&#xff0c;这个地址就称为函数的指针&#xff0c;函数名代表函数的入口地址。函数三要素&#xff1a; 名称、参数、返回值。C语言中的函数有自己特定的类型。c语言中通过…

多元回归分析 | ELM极限学习机多输入单输出预测(Matlab完整程序)

多元回归分析 | ELM极限学习机多输入单输出预测(Matlab完整程序) 目录 多元回归分析 | ELM极限学习机多输入单输出预测(Matlab完整程序)预测结果评价指标基本介绍程序设计参考资料预测结果 评价指标 -----------ELM结果分析-------------- 均方根误差(RMSE):0.55438 测试集…

webgis高德地图

webgis高德地图 首先准备工作,注册一个高德地图账号,然后在创建一个新应用生一个key跟appId 高德开放平台 接着创建一个html页面 高德配置手册 <style>* {margin: 0;padding: 0;}#

如何维护固态继电器?

固态继电器是SSR的简称&#xff0c;是由微电子电路、分立电子器件和电力电子功率器件组成的非接触式开关。隔离装置用于实现控制端子与负载终端之间的隔离。固态继电器的输入端使用微小的控制信号直接驱动大电流负载。那么&#xff0c;如何保养固态继电器呢&#xff1f; 在为小…

Editor工具开发基础一:顶部菜单栏拓展

一.创建脚本路径 不管是那种工具 都需要在工程里创建一个Editor文件夹 来存放工具.cs文件 二.特性 MenuItem 特性 修饰静态方法 三个构造函数 public MenuItem(string itemName); public MenuItem(string itemName, bool isValidateFunction); public MenuItem(string itemN…

手工测试用例就是自动化测试脚本——使用ruby 1.9新特性进行自动化脚本的编写

昨天因为要装watir-webdriver的原因将用了快一年的ruby1.8.6升级到了1.9。由于1.9是原生支持unicode编码&#xff0c;所以我们可以使用中文进行自动化脚本的编写工作。 做了简单的封装后&#xff0c;我们可以实现如下的自动化测试代码。请注意&#xff0c;这些代码是可以正确运…

【2023考研数学二考试大纲】

文章目录I 考试科目II考试形式和试卷结构一、试卷满分及考试时间二、答题方式三、试卷内容结构四、试卷题型结构III考查内容【高等数学】一、函数、极限、连续二、一元函数微分学三、一元函数积分学四、多元函数微积分学五、常微分方程【线性代数】一、行列式二、矩阵三、向量四…

新手入门吉他推荐,第一把吉他从这十款选绝不踩雷!初学者吉他选购指南【新手必看】

一、新手购琴注意事项&#xff1a; 1、预算范围 一把合适的吉他对于初学者来说会拥有一个很好的音乐启蒙。选一款性价比高&#xff0c;做工材料、音质和手感相对较好的吉他自然不会是一件吃亏的事。**初学者第一把琴的预算&#xff0c;我觉得最低标准也是要在500元起&#xf…

数字IC设计需要学什么?

看到不少同学在网上提问数字IC设计如何入门&#xff0c;在学习过程中面临着各种各样的问题&#xff0c;比如书本知识艰涩难懂&#xff0c;有知识问题难解决&#xff0c;网络资源少&#xff0c;质量参差不齐。那么数字IC设计到底需要学什么呢&#xff1f; 首先来看看数字IC设计…

我们来说说蹿红的AIGC到底是什么?ChatGPT又是什么?

近期&#xff0c;人工智能&#xff08;AI&#xff09;领域动作频频&#xff0c;OPENAI公司Chat GPT的出现&#xff0c;标志着人工智能的研究与应用已经进入了一个崭新的发展阶段&#xff0c;国内腾讯、阿里巴巴、百度、易网、国外微软、谷歌、苹果、IBM、Amazon&#xff0c;等互…

YOLOv5的训练调优技巧

本文编译自英文原文https://github.com/ultralytics/yolov5/wiki/Tips-for-Best-Training-Results&#xff0c;文章解释了如何提高Yolov5的mAP和训练效果。大多数时间&#xff0c;在没有改变模型或是训练配置的情况下&#xff0c;如果能够提供足够多的数据集以及好的标注&#…

理解随机游走

随机游走 基本思想 从一个或一系列顶点开始遍历一张图。在任意一个顶点&#xff0c;遍历者将以概率1-a游走到这个顶点的邻居顶点&#xff0c;以概率a随机跳跃到图中的任何一个顶点&#xff0c;称a为跳转发生概率&#xff0c;每次游走后得出一个概率分布&#xff0c;该概率分布…

【前端】CSS3弹性布局、媒体查询实现响应式布局和自适应布局

文章目录弹性布局基本概念容器&#xff08;container&#xff09;的属性容器成员&#xff08;item&#xff09;的属性媒体查询响应式布局自适应布局参考弹性布局 基本概念 任何一个容器都可以指定为 Flex 布局。 display:flex;行内元素也可以&#xff1a; display:inline-f…

Apollo规划模块代码学习(2): 轨迹规划流程理论基础详解(lane follow场景为例)

文章目录1、轨迹规划基础2、Frenet坐标系3、路径规划和速度规划4、轨迹优化&#xff08;QP过程&#xff09;5、规划流程(lane follow场景为例)1、规划模块流程2、Apollo中各场景3、Lane_follow场景中task本文以具体场景Lane follow为例梳理具体的轨迹规划算法流程。详细介绍轨迹…

关于selenium的等待

目录 隐式等待 显式等待 注意事项 隐式等待 简单来说&#xff1a;在规定的时间范围内&#xff0c;轮询等待元素出现之后就立即结束。 如果在规定的时间范围内&#xff0c;元素仍然没有出现&#xff0c;则会抛出一个异常【NoSuchElementException】&#xff0c;脚本停止运行…

【Linux学习笔记】2.Linux 系统启动过程及系统目录结构

前言 本章介绍Linux的系统启动过程和系统目录结构。 Linux 系统启动过程 linux启动时我们会看到许多启动信息。 Linux系统的启动过程并不是大家想象中的那么复杂&#xff0c;其过程可以分为5个阶段&#xff1a; 内核的引导。运行 init。系统初始化。建立终端 。用户登录系…

【ARM架构】armv8 系统安全概述

ARMv8-A 系统中的安全 一个安全或可信的操作系统保护着系统中敏感的信息&#xff0c;例如&#xff0c;可以保护用户存储的密码&#xff0c;信用卡等认证信息免受攻击。 安全由以下原则定义&#xff1a; 保密性&#xff1a;保护设备上的敏感信息&#xff0c;防止未经授权的访问…

C#值传递、引用传递、输出传递详解

C#值传递、引用传递、输出传递详解1、值传递2、引用传递3、输出传递4、ref 和 out导读&#xff1a; 1&#xff0c;值传递时&#xff0c;为什么被调用的方法中的形参值的改变不会影响到相应的实参&#xff1f; 答&#xff1a;因为按值传递时&#xff0c;系统首先为被调用的方法的…