Java集合 高频面试题,限时发布

此时 HashMap 的底层实现是数组+链表的形式,HashMap 通过 key 的 hashCode() 经过扰动函数处理过后得到 hash 值,然后利用公式((n - 1) & hash)判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存⼊的元素的 hash 值以及 key 值是否相同,如果均相同则直接覆盖,如果值不同,则通过拉链法来解决哈希冲突。

所谓 “拉链法” 就是:将链表和数组相结合。也就是说创建⼀个链表数组,数组中每⼀格就是⼀个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

JDK1.8 及之后

对于插入,相比于之前的版本, JDK1.8 在解决哈希冲突时有了较⼤的变化,当链表长度大于阈值(默认为 8)(将链表转换成红⿊树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红⿊树)时,将链表转化为红⿊树,以减少搜索时间。

对于移除,当同一个索引位置的节点在移除后小于 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点。

4.2 为什么要改成“数组+链表+红黑树”


主要是为了提升在 hash 冲突严重时(链表过长)的查找性能,使用链表的查找性能是 O(n)(线性级),而使用红黑树是 O(logn)(对数级)。

4.3 面试回答


请问 HashMap 的底层实现是怎么样的?

在 JDK1.8 之前的话,HashMap 的底层是通过数组+链表的形式来实现的,在 JDK1.8 及之后的话,则是通过数组+链表/红黑树的形式来实现,这两者的区别在于,当哈希冲突严重时,后者会将链表转换成红黑树来存储,从而提升查找性能。

2 什么是哈希冲突

因为 HashMap 在存储数据时是根据键的哈希值所对应的位置来存放的,然后可能存在键不同,对应的位置相同的情况,这种情况便是哈希冲突,在实际中,常采用拉链法来解决,拉链法指的是将链表和数组相结合,数组中每⼀格就是⼀个链表,若遇到哈希冲突,则将冲突的值加到链表后即可。

3 什么是红黑树,红黑树如何进行自调整,与哈希表的区别

红黑树是一种可以自平衡的二叉查找树。(两头乌,路径上不能有两个连续的红色节点)

红黑树通过变色、左旋转和右旋转来保持平衡。

与哈希表的区别:

  1. 在增改查性能上,红黑树的性能是稳定且高效的,而哈希表当哈希碰撞严重时,时间复杂度会达到线性级;

  2. 红黑树是有序的,哈希表是无序的;

  3. 相对来说,红黑树占用的内存更小。

5 HashSet 的相关问题

==================================================================================

5.1 HashSet 的底层实现


HashSet 的底层实际上是通过 HashMap 来实现的,除了实现 Set 接口所规范的方法外,其它方法都是直接调用 HashMap,HashSet 将对应元素存放在 HashMap 的键上,与此相对应的值存放了一个静态的 final 修饰的 Object 对象。

5.2 HashSet 如何检查重复


当把对象加入到 HashSet 中时,HashSet 会比较该对象的哈希值是否和 HashSet 中已经存在的对象的哈希值相等,如果不相等则直接加入;如果相等,则调用 equals() 方法来检查这两个哈希值相等的对象是否真的相同,如果两者相同,HashSet 就不会让其加⼊操作成功。

6 ConcurrentHashMap 的相关问题

============================================================================================

6.1 ConcurrentHashMap 的底层具体实现


JDK1.8 之前

请添加图片描述

⾸先将数据分为⼀段⼀段的存储,然后给每⼀段数据配⼀把锁,当⼀个线程占⽤锁访问其中⼀个段数据时,其他段的数据也能被其他线程访问。

ConcurrentHashMap 是由 Segment 数组 + HashEntry 数组 + 链表组成。

⼀个 ConcurrentHashMap ⾥包含⼀个 Segment 数组,Segment 每一格包含着一个 HashEntry 数组, HashEntry 数组的每一格则包含一条链表,每个 Segment 元素守护着 HashEntry 数组⾥的元素,当对 HashEntry 数组的数据进⾏修改时,必须⾸先获得对应的 Segment 的锁。

JDK1.8 之后

请添加图片描述

ConcurrentHashMap 取消了 Segment 分段锁,采⽤ CAS(Compare And Swap,比较并交换) 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。在链表⻓度超过⼀定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红⿊树(寻址时间复杂度为 O(log(N)))

synchronized 只锁定当前链表或红黑树的首节点,这样只要 hash 不冲突,就不会产生并发,效率⼜提升 N 倍。

6.2 面试回答


1 ConcurrentHashMap 相对于 HashMap 做了哪些优化

ConcurrentHashMap 相对于 HashMap 是线程安全的,它采用了分段锁的形式来保证了线程安全;

JDK1.8 之前的话,它是由 Segment 数组 + HashEntry 数组 + 链表组成的,其中 Segment 数组的每一格包含着一个 HashEntry 数组, HashEntry 数组的每一格则包含一条链表,当对链表中的数据进行修改时,必须首先获得对应的 Segment 的锁。

在 JDK1.8 及之后,取消了 Segment 分段锁,采用 CAS 和同步的方式来保证并发安全。数据结构跟 HashMap 类似,也是采用数组+链表/红黑树,同时同步关键字只锁定当前链表或红黑树的首节点,这样只要 hash 不冲突,就不会产生并发。

7 相似集合的异同

============================================================================

7.1 ArrayList 和 LinkedList 的异同


  1. 底层数据结构: Arraylist 底层使⽤的是 Object 数组; LinkedList 底层使⽤的是双向链表;

  2. 线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

  3. 是否支持快速随机访问: LinkedList 不⽀持⾼效的随机元素访问,⽽ ArrayList ⽀持。快速随机访问就是通过元素的序号快速获取元素对象(对应于 get(int index) ⽅法);

  4. 插入和删除是否受元素位置的影响: ① ArrayList 采⽤数组存储,所以插⼊和删除元素的时间复杂度受元素位置的影响。 ⽐如:执⾏ add(E e) ⽅法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插⼊和删除元素的话( add(int index, E element) )时间复杂度就为 O(n-i)。因为在进⾏上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执⾏向后位/向前移⼀位的操作。 ② LinkedList 采⽤链表存储,如果是要在指定位置 i 插⼊和删除元素的话( (add(int index, E element) ) 时间复杂度近似为 o(n)) 因为需要先移动到指定位置再插⼊;(总的来说,查找遍历用 ArrayList,增加删除用 LinkedList

  5. 内存空间占用: ArrayList 的空间浪费主要体现在在列表的结尾会预留⼀定的容量空间(因为扩容时数组的大小不太可能是刚刚好的),⽽ LinkedList 的空间花费则体现在它的每⼀个元素都比 ArrayList 需要更多的空间(因为要存放前驱、后继以及数据)。

7.2 ArrayList 和 Vector 的异同


  • ArrayList 是 List 的主要实现类,底层使⽤ Object[ ] 存储,线程不安全 ;

  • Vector 是 List 的古老实现类,底层使⽤ Object[ ] 存储,线程安全的,因此效率低;

线程安全是多线程编程时的计算机程序代码中的一个概念,在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

线程的安全是以牺牲效率为代价的,所谓线程安全就是多了个加锁、解锁的操作,比如100亿个操作中都要加锁和解锁,线程是安全了,但效率就下降了。而有些软件是以效率为主的,为了提高效率,就少了加锁,解锁的操作,虽然容易出现并发访问问题,但效率却提高了。

7.3 HashMap 和 Hashtable 的异同


  1. 线程是否安全: HashMap 是非线程安全的, HashTable 是线程安全的,因为 HashTable 内部的⽅法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使⽤ ConcurrentHashMap 吧!);

  2. 效率: 因为线程安全的问题, HashMap 要比 HashTable 效率高⼀点;

  3. 对 Null key 和 Null value 的⽀持: HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有⼀个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException;

  4. 初始容量大小和每次扩充容量大小的不同 : ① 创建时如果不指定容量初始值, Hashtable 默认的初始⼤⼩为 11,之后每次扩充,容量变为原来的 2n+1;HashMap 默认的初始化⼤⼩为 16,之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使⽤你给定的⼤⼩,⽽ HashMap 会将其扩充为 2 的幂次⽅⼤⼩,也就是说 HashMap 总是使⽤ 2 的幂作为哈希表的⼤⼩;

  5. 底层数据结构: JDK1.8 之前 HashMap 使用的是数组和链表结合在一起的链表散列,JDK1.8 及之后使用的是 数组+链表+红黑树的方式;Hashtable 底层是数组+链表;

7.4 HashMap 和 ConcurrentHashMap 的异同


  • HashMap 是线程不安全的,但速度快;

  • ConcurrentHashMap 是线程安全的,但其并不是像 Hashtable 一样给每一个方法都加上同步修饰(整体锁),而且使用的是分段锁,因此如果需要保证线程安全,就使用这个 Map;

  • Hashtable 是线程安全的,但效率实在太低,目前已基本被淘汰。

7.5 ConcurrentHashMap 和 Hashtable 的异同


  1. 实现线程安全的方式:① 在 JDK1.7 的时候,ConcurrentHashMap 将数据分为⼀段⼀段的存储,然后给每⼀段数据配⼀把锁,当⼀个线程问其中⼀个段数据时,其他段的数据也能被其他线程访问,即分段锁。到 JDK1.8 的时候,ConcurrentHashMap 取消了Segment 分段锁,采⽤ CAS 和 synchronized 来保证并发安全。② Hashtable 使⽤ synchronized 修饰所有方法来保证线程安全,效率十分低下,当一个线程访问同步方法时,其它线程也访问同步方法时便会进入阻塞或者轮询状态,即全表锁

  2. 底层数据结构:JDK1.7 的 ConcurrentHashMap 底层采⽤ 分段的数组+链表 实现,JDK1.8 采⽤的数据结构跟 HashMap 的结构⼀样的数组+链表/红⿊⼆叉树。Hashtable 底层是用数组+链表来实现的。

7.6 HashMap 和 HashSet 的异同


HashSet 的底层是基于 HashMap 来实现的,除了实现 Set 接口所必须实现的方法外,其它方法都是直接调用 HashMap 的方法。

HashSet 不能有重复的元素,HashMap 不允许有重复的键。HashSet 中的值实际上就是放在了 HashMap 中的 key 中,此时在 HashMap 的 value 则存储了一个 PRESENT,它是一个static final 的 Object 对象。

| HashMap | HashSet |

| :-: | :-: |

| 实现 Map 接⼝ | 实现 Set 接⼝ |

| 存储键值对 | 仅存储对象 |

| 调⽤ put() 向 map 中添加元素 | 调⽤ add() ⽅法向 Set 中添加元素 |

| HashMap 使⽤键(Key)计算 hashcode | HashSet 使⽤成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以还需要 equals() ⽅法⽤来判断对象的相等性,本质上该成员对象也是一个 key 值。 |

7.7 TreeSet 和 HashSet 的异同

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

读者福利

秋招我借这份PDF的复习思路,收获美团,小米,京东等Java岗offer

更多笔记分享

秋招我借这份PDF的复习思路,收获美团,小米,京东等Java岗offer

进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-PRtBBFNM-1711698233837)]

读者福利

[外链图片转存中…(img-HEIMg8pN-1711698233838)]

更多笔记分享

[外链图片转存中…(img-G1oEGyHJ-1711698233838)]

  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值