JAVA面经整理(2)

一)解决哈希冲突的方法有哪些?

哈希冲突指的是在哈希表中,不同的键值映射到了相同的哈希桶,也就是数组索引,导致键值对的冲突

1)设立合适的哈希函数:通过哈希函数计算出来的地址要均匀的分布在整个空间中

2)负载因子调节:

2.1)开放地址法:

1)当发生哈希冲突时,如果哈希表中没有装满,说明哈希表中一定还有空余位置,那么可以把key放到冲突位置的下一个空位置去,从发生冲突的位置开始,依次次向后探测,直到找到下一个空位置为止

2)当插入数据的时候:先通过哈希函数计算到待插入元素在哈希表中的位置,如果该位置中没有元素就直接插入新元素,如果该位置中有元素就使用线性探测找到下一个空位置,直接插入元素,从冲突的位置开始,向后进行探测,把元素放进去

3)但是他会容易把冲突的元素会放在一起,还不可以随意地删除元素,例如把4删掉,44查起来,44进行查询需要依赖于4下标会受到影响,需要加上一个标志位flag,没删除是0,删除了是1,所以说在我们进行闭散列来进行处理哈希冲突的时候,不可以随便删除哈希表中原有的元素,若删除此元素会影响其他元素的查找

2.2)二次探测:

1)线性探测的缺点是将产生冲突的元素放到了一起,堆积到了一块,这与其和找下一个空位置有关系,但是二次探测为了解决该问题,找下一个空位置的方法发生了变化

2)放的位置在H(i)=(H(0)+i^2)%m,保证放的数据不会紧挨着,i代表第几次冲突,空间利用率比较低,更加均匀的分配了元素

2.3)链式地址法:

2.4)再哈希法:当发生哈希冲突的时候再次使用另一个哈希函数计算出一个新的哈希值,然后将元素插入到对应的位置

二)HashMap底层是如何进行实现的

Hashmap集合在jdk1.7的版本(采用头插法),底层是通过数组加链表实现的,在jdk1.8的版本底层是中是通过数组+链表+红黑树实现的,为什么要引入红黑树呢,因为链表的查询效率太低,例如在老版本中通过key算出的index(不同的数据通过哈希函数算出的index相同)得知发生冲突的情况下,会存放到同一个链表中,查询效率非常低,因为会从头查到尾,时间复杂度就是O(n),所以在jdk1.8(采用尾插法)中引入了红黑树,当数组长度大于64况且链表长度大于8时,就会直接将链表转化成红黑树

HashMap的数据结构在jdk1.8之前是数组+链表,为了解决数据量过大、链表过长是查询效率会降低的问题变成了数组+链表+红黑树的结构,利用的是红黑树自平衡的特点。

链表的平均查找时间复杂度是O(n),红黑树是O(log(n))

三)为什么HashMap一定要使用红黑树?

1)AVL树,因为AVL树和插入和删除节点,整体的性能不如红黑树,在AVL树中,每一个节点的平衡因子是左子树高度和右子树高度的差值,平衡因子只能是-1,1和0,当插入和删除元素的时候,任何节点的平衡因子超过了这个范围,就要通过左旋,右旋,左右双旋右左双旋这样的操作来让AVL树保持平衡,但是红黑树相对来说比较宽松,插入和删除操作会导致比较少的旋转操作,因此在频繁的插入和删除的条件下,红黑树的性能可能要高于AVL树

2)二叉搜索树:左右节点极其不平衡,可能会退化成链表

3)对于结点平衡的要求没有那么特别高,相比于AVL树来说相对来说比较宽松

四)什么是负载因子?

负载因子也叫做扩容因子,本质上是一个用于HashMap何时进行扩容的参数,计算方法就是存入表中的元素的个数/表的大小,当HashMap存储的键值对数量超过了HashMap总容量乘以负载因子的时候,就会发生扩容操作

如果所有数组存满了就扩容,那么随着时间的推移,插入时间就越长,哈希冲突也会变得越来越高

五)为什么HashMap的负载因子是0.75

不仅要平衡性能也要平衡空间的利用率,这个值被认为是时间和空间效率上面之间的一个较好的平衡点

1)当负载因子比较大的时候,那么就意味着发生扩容的时间比较晚,此时哈希表中存储更多的元素才会发生扩容,空间利用率就会比较高,但是发生哈希冲突碰撞的概率就会比较大,增删查改一个元素的时间就会变长;

2)当负载因子比较小的时候,那么扩容会比较早,此时哈希表数组中存储的元素比较少的时候就发生哈希冲突从而进行扩容了,哈希冲突发生的概率比较低插入的时间会变快,但是空间利用率就会变得非常的低,增删改查的效率会比较高;

六)说一下hashcode和equals的区别

1)hashcode是指定当前引用类型当前元素,当需要将一个引用类型放到散列表中,就需要重写hashcode生成在数组中的下标

2)equals方法是需要在hashcode定义的数组下标中,遍历链表,判断哪个key是和当前的key是相同的,比较引用类型所指向的对象中的具体内容

衍生问题:

一)如果两个数据的hashcode相同,equals一定相同吗?

不一定,但是这两个数据一定哈希到了同一个位置

二)如果两个数据的hashcode不同,equals不一定相同吗?

一定不相同,在数组中的位置都不一样; 如果两个数据的equals相同,那么内容一定相同,此时的hashcode也是相同的;

三)如果newhashMap(19),那么哈希表的数组有多大?

当指定大小的时候,哈希表的数组容量一定是2的多少次幂,所以找超过指定容量的2的多少次幂最靠近19,2的5次幂是32;

四)hashmap什么时候开辟bucket数组,占用内存?

HashMap当第一次put元素的时候,默认容量是16,最接近值的2次幂

五)hashmap什么时候会进行扩容?

当负载因子超过0.75的时候

六)Hashmap链表长度为8时转换成红黑树,你知道为什么是8吗?

1)当链表长度大于或等于8的时候,如果同时还满足容量大于或等于64的时候,就会把链表转换为红黑树,同样,后续如果由于删除或者其他原因调整了大小,当红黑树的节点小于或等于 6 个以后,又会恢复为链表形态;

2)每次遍历一个链表,平均查找的时间复杂度是O(n),n 是链表的长度,由于红黑树有自平衡的特点,可以防止不平衡情况的发生,所以可以始终将查找的时间复杂度控制在log(n)

3)最初链表还不是很长,所以可能 O(n) 和 O(log(n)) 的区别不大,但是如果链表越来越长,那么这种区别便会有所体现。所以为了提升查找性能,需要把链表转化为红黑树的形式。

4)通常如果 hash 算法正常的话,那么链表的长度也不会很长,那么红黑树也不会带来明显的查询时间上的优势,反而会增加空间负担。

5)个人觉得是数据量较少时,红黑树会频繁的发生左旋或者右旋,浪费cpu性能,所以加入了链表,单个 TreeNode 需要占用的空间大约是普通链表Node 的两倍,而当桶中节点数由于移除或者 resize 变少后,又会变回普通的链表的形式,以便节省空间,如果要性能就需要牺牲空间,要空间就要牺牲性能;

七)HashMap的put操作:

1)首先会调用函数叫做hash(key)这个函数最终的返回值是根据当前的key返回一个32位的散列码也就是hashcode,具体做法是key.hashcode()^(key.hashcode<<<16),低16位和高16位进行位运算,保证数据均匀分布;

但是如果说当前元素插入的是一个null值,那么这个key则会默认放到数组的0号位置

2)接下来就会调用putVal()方法

2.1)首先会进行判断哈希表是否为空,或者是说数组的长度等于0,那么直接进行初始化数组

2.2)然后扩容完成之后,会根据之前通过hash函数计算出来的散列码来进行计算当前这个key要存放到数组的哪一个下标,在这里是通过(n-1)&hashcode来计算位置,但是这里面为什么不是hashcode%n,因为对于任意的n来说(n-1)&hashcode=hashcode%n(是任意数字)

2.3)判断数组当前下标有没有元素,如果没有,就进行设置该节点

2.4)再进行判断当前数组的位置是否和当前要插入的key相同,如果相同,直接覆盖value

2.5)如果当前数组的位置不等于key,那么再进行判断当前是否是红黑树,如果是红黑树,直接插入节点

2.6)如果不是红黑树,说明当前结构是链表,那么直接遍历链表,找到链表的最后一个位置,那么直接使用尾插法插入当前元素,如果在遍历的过程中发现key已经重复了,那么直接覆盖value

2.7)如果数组长度大于64&&链表长度大于8,那么直接转化成红黑树进行处理

3)如果超过负载因子,进行扩容

1)当调用没有参数的构造方法的时候

当数组长度是0或者数组的引用为空的时候,第一次put操作的时候,就会执行reasize()的方法来进行扩容,默认的初始容量是16;

2)根据哈希值来进行计算索引的时候,在寻找数组的下标的时候,在咱们之前的代码中时使用Key的哈希值%数组长度,但是在HashMap的源码中是用数组下标=(数组长度-1)&hash)

4&15==4%16hash%n-1的值(也就是得到位置)相等,位运算的速度更快,效率更高;

八)为什么HashMap数组的容量必须是2^N? 

(n-1)&hash保证n是偶数

1)如果n是偶数,那么n-1的最后一位一定是1,当与hash码(hash码最后一位有可能是进行0也有可能是1)&运算的时候,得到的最后一位是0或者1;

最终得到的数组下标可能是奇数也有可能是偶数

2)如果n是奇数,那么n-1的最后一位是0,那么与hash函数进行&操作的时候,会得到的下标的最后一位一定为0;最后只能得到偶数下标;

3)就是说我们以初始容量为16来进行举例,16-1=15,那么15的二进制序列就是001111,我们可以看出一个奇数二进制最后一位必然是1,当一个hash值参与运算的时候,最后一位可能是1,也有可能是0,当一个偶数和hash值进行与运算的时候最后一位必然是0,会造成有些位置永远也无法映射上值

4)保证数组容量是偶数,才可以保证最后的下标即是奇数下标又是偶数下标

九)HashMap和HashTable有什么区别? 

1)两者最主要的区别在于Hashtable是线程安全性能比较低,而HashMap则非线程安全安全性比较高

2)HashMap可以使用null作为key,不过建议还是尽量避免这样使用,HashMap以null作为key时,总是存储在table数组的第一个节点上,但是hashtable这样的线程安全的集合类容器不允许插入空的key和value的,在咱们ConcurrentHashMap和HashTable的源码当中,如果key为空,或者value为空,直接抛出空指针异常
4)HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
5)HashMap扩容时是当前容量翻倍即:capacity*2,Hashtable扩容时是容量翻倍+1即:capacity*2+1。
6)HashMap数据结构:数组+链表+红黑树,Hashtable数据结构:数组+链表。

7)计算哈希值也是不同的:hashMap先计算出哈希值,然后无符号右移16位,然后再进行按位异或操作,但是hashTable直接按位与0x7FFFF得到散列码;

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
十)HashMap为什么会产生死循环和数据覆盖等问题? 

HashMap产生死循环的原因就是因为数据扩容是头插法,形成环形链表

再加上是多线程的并发扩容操作造成了死循环的问题

死循环产生步骤1:

死循环是并发HashMap进行扩容导致的,并发扩容的第一步,线程T1和线程T2需要同时进行扩容操作,此时T1线程和T2线程指向的是链表的头节点A,而T1.next和T2.next指向的下一个节点就是B

死循环产生步骤2:此时T1线程扩容完成,T2线程被唤醒,也开始执行扩容操作

死循环执行步骤3:因为T2线程被唤醒之后,T1线程刚刚完成扩容,T1完成扩容之后B的下一个节点是A,但是此时T2线程此时指向的头结点是A,下一个节点是B,T1执行完的顺序是从A到B,T2执行完的顺序是从B到A,此时就发生了死循环

十一)HashMap产生的数据覆盖的问题:

数据覆盖问题:多个线程同时并发的向同一个位置去更新元素或者是添加元素

先要想某一个位置添加元素,这并不是一个原子性的操作

1)先判断这个位置是否可以添加元素,结果判断当前位置没有元素

2)因为第一步已经判断当前位置没有元素,所以直接将这个元素添加到此位置

当两个线程并发执行这两个操作的时候,可能就会出现问题

1)线程T1进行添加的时候,通过key计算出一个hashcode计算出数组的一个位置,判断这个位置可以进行插入元素了,但还没有真正地进行插入操作,时间片就用完了,此时线程1已经判断好这个位置是空了,刚刚要进行插入操作,就被调度器给抢走了

2)线程2也想要进行插入操作,通过key计算出一个hashcode计算出数组的一个位置恰好和线程1要插入的元素的位置相同,并且T2要进行插入的数据key和T1要进行插入的数据key是不相同的,但是线程1和线程2计算出来的hashcode是相同的,由于此位置没有任何元素,T1只是进行判断,刚想要插入值就被调度器给抢走了,于是此时线程2先判断当前位置没有值,就把自己T2线程的值存入到当前位置

3)T1线程恢复执行之后,因为非空判断已经执行完了,T1线程当前是无法感知当前位置已经有值了,因为已经判断完了,于是就把自己的值插入到了该位置,于是T2线程插入的值就被覆盖了

1)分段锁就是针对数组元素进行分组,多个哈希桶共同分配一把锁,Concurrent的segment里面会存在着多个哈希桶(哈希桶1,哈希桶2,哈希桶3),当线程1和线程2想要并发的修改哈希桶1和哈希桶2,就会排队等待获取到这个片段锁,线程1获取锁之后修改哈希桶1,线程2获取到锁之后才能对哈希桶2进行操作,因为锁的粒度太大,性能太低,大家使用的是同一把锁,可能会导致程序的执行效率太低,比如说有100个线程并发修改哈希桶1,哈希桶2,哈希桶3,那么这100个线程都是竞争着同一把锁,所以就会导致线程安全问题,但是假设我们来看一下,并发修改哈希桶1和并发修改哈希桶2似乎也不会产生线程安全问题把

2)JDK1.8之后,使用粒度更小的锁,一个哈希桶对应一把锁,所以此时锁的粒度越小,高并发情况下,程序运行的效率变得更高

一定是锁的粒度越小越好吗?

不是所有的情况下锁的粒度都是越小越好的,高并发情况下才可以发挥出锁粒度比较小的优势的,但是相比来说非高并发情况下使用Segment分段锁比较合适,因为加锁是存在着性能消耗的,如果加更多的锁会有更多的消耗,如果有更多的消耗自己又用不上的情况下,浪费了资源,性能也比较高

3)ReentranLock是AQS实现的

4)读写锁是ReentranLock的子锁

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

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

相关文章

【vue】vue+easyPlayer 实现宫格布局及视频播放

由于业务需要&#xff0c;ant-design-vue框架集成easyPlayer.js作为视频播放器。EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC等多种音视频编码格…

王江涛十天搞定考研词汇

学习目标&#xff1a; 考研词汇 学习内容&#xff1a; 2023-9-17 第一天考研词汇 学习时间&#xff1a; 开始:2023-9-17 结束:进行中 学习产出&#xff1a; 2023-9-17intellect智力&#xff1b;知识分子intellectual智力的&#xff1b;聪明的intellectualize使...理智化&a…

堆的实现(C版)

普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储&#xff0c;需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事&#xff0c;一个是…

软件设计模式系列之十二——外观模式

在软件设计中&#xff0c;经常会遇到需要与复杂子系统进行交互的情况。为了简化客户端与子系统之间的交互&#xff0c;提高系统的可维护性和可用性&#xff0c;外观模式应运而生。外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供一个统…

激活函数总结(四十一):激活函数补充(ShiLU、ReLUN)

激活函数总结&#xff08;四十一&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 ShiLU激活函数2.2 ReLUN激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PReLU、Swish、ELU、SELU、GELU、Softmax、Sof…

腾讯云16核服务器性能测评_轻量和CVM配置大全

腾讯云16核服务器配置大全&#xff0c;CVM云服务器可选择标准型S6、标准型SA3、计算型C6或标准型S5等&#xff0c;目前标准型S5云服务器有优惠活动&#xff0c;性价比高&#xff0c;计算型C6云服务器16核性能更高&#xff0c;轻量16核32G28M带宽优惠价3468元15个月&#xff0c;…

简单的手机电脑无线传输方案@固定android生成ftp的IP地址(android@windows)

文章目录 abstractwindows浏览android文件环境准备客户端软件无线网络链接步骤其他方法 手机浏览电脑文件公网局域网everythingpython http.server 高级:固定android设备IP准备检查模块是否生效 windows 访问ftp服务器快捷方式命令行方式双击启动方式普通快捷方式映射新的网络位…

第六次面试、第一次复试

第六面&#xff1a; hr迟到&#xff0c;说是搞错了以为线下&#xff0c;我打电话过去才开始&#xff0c;问我想电话面还是视频&#xff0c;果断电话面 自我介绍 介绍了一下公司的工作 ................. 项目拷打&#xff1a; grpc数据如何传输的如何调用两个接口如何获取…

Android 12 源码分析 —— 应用层 六(StatusBar的UI创建和初始化)

Android 12 源码分析 —— 应用层 六&#xff08;StatusBar的UI创建和初始化) 在前面的文章中,我们分别介绍了Layout整体布局,以及StatusBar类的初始化.前者介绍了整体上面的布局,后者介绍了三大窗口的创建的入口处,以及需要做的准备工作.现在我们分别来细化三大窗口的UI创建和…

利用大模型知识图谱技术,告别繁重文案,实现非结构化数据高效管理

我&#xff0c;作为一名产品经理&#xff0c;对文案工作可以说是又爱又恨&#xff0c;爱的是文档作为嘴替&#xff0c;可以事事展开揉碎讲清道明&#xff1b;恨的是只有一个脑子一双手&#xff0c;想一边澄清需求一边推广宣传一边发布版本一边申报认证实在是分身乏术&#xff0…

模块化软件架构:使用单体、微服务和模块化单体的优缺点

【squids.cn】 全网zui低价RDS&#xff0c;免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 近年来&#xff0c;微服务架构在大多数软件解决方案中已经处于领先地位&#xff0c;而且在很多情况下&#xff0c;它经常被选择为我们开始开发的架构。然而&#xff0c…

解密list的底层奥秘

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

软件定制APP开发步骤分析|小程序

软件定制APP开发步骤分析|小程序 软件定制开发步骤&#xff1a; 1.需求分析&#xff1a; 这是软件定制开发的第一步&#xff0c;也是最关键的一步。在这个阶段&#xff0c;软件开发团队需要与客户进行沟通&#xff0c;了解客户的具体需求和期望。通过讨论和交流&#xff0c;确…

前后端分离管理系统day01---Springboot+MybatisPlus

目录 目录 软件 基础知识 一创建后端项目 注意&#xff1a; 删除多余项 创建测试类 二 加入mybatis-plus依赖支持 1.加入依赖码 2.创建数据库实例/创建用户表/插入默认数据 创建数据库实例 创建表 插入数据 3.配置yml文件 注意&#xff1a;wms01必须是数据库的名字&…

[maven] scopes 管理 profile 测试覆盖率

[maven] scopes & 管理 & profile & 测试覆盖率 这里将一些其他的特性和测试覆盖率&#xff08;主要是 jacoco&#xff09; scopes maven 的 scope 主要就是用来限制和管理依赖的传递性&#xff0c;简单的说就是&#xff0c;每一个 scope 都有其对应的特性&…

WebGL 初始化着色器

目录 前言 初始化着色器的7个步骤 创建着色器对象&#xff08;gl.createShader&#xff08;&#xff09;&#xff09; gl.createShader&#xff08;&#xff09;规范 gl.deleteShader&#xff08;&#xff09;规范 指定着色器对象的代码&#xff08;gl.shaderSource&…

Linux高并发服务器开发第四章:Linux网络编程

1. 网络结构模式 C/S结构 简介 服务器 - 客户机&#xff0c;即 Client - Server&#xff08;C/S&#xff09;结构。C/S 结构通常采取两层结构。服务器负责数据的管理&#xff0c;客户机负责完成与用户的交互任务。客户机是因特网上访问别人信息的机器&#xff0c;服务器则是…

【结构型】代理模式(Proxy)

目录 代理模式(Proxy)适用场景代理模式实例代码&#xff08;Java&#xff09; 代理模式(Proxy) 为其他对象提供一种代理以控制对这个对象的访问。Proxy 模式适用于在需要比较通用和复杂的对象指针代替简单的指针的时候。 适用场景 远程代理 (Remote Proxy) 为一个对象在不同…

10分钟设置免费海外远程桌面

前言 本教程将向您介绍如何使用 Amazon Lightsail 服务的免费套餐轻松搭建属于您的远程桌面。依托于 Amazon 全球可用区&#xff0c;您可以在世界各地搭建符合您配置需求的远程桌面。 本教程需要先拥有亚马逊云科技海外账户。现在注册亚马逊云科技账户可以享受12个月免费套餐…

Python与数据分析--每天绘制Matplotlib库实例图片3张-第1天

目录 1.实例1--Bar color demo 2.实例2--Bar Label Demo 3.实例3--Grouped bar chart with labels 1.实例1--Bar color demo import matplotlib.pyplot as plt # 支持中文 plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus…