1.5万字长文Java集合与数据结构面试题(注:该篇博客将会持续维护 最新维护时间:2024年11月25日)

🧸本篇博客重在讲解Java集合与数据结构面试题,将会实时更新,欢迎大家添加作者文末联系方式交流

📜JAVA面试题专栏JAVA崭新面试题——2024版_dream_ready的博客-CSDN博客

📜作者首页: dream_ready-CSDN博客

📜有任何问题都可以评论留言,作者将会实时回复

📜未来将会更新数据结构面试题以及Spring和MySQL等等的面试题,将会涵盖JAVA后端所有技术框架,敬请期待!!!

注:本篇博客题目顺序是精心制作的,建议想要阅读完整篇博客的同学按着顺序阅读!

目录

1、Java的集合类有哪些?

2、集合和数组的区别?

3、ArrayList和LinkedList的区别

4、ArrayList和Vector有什么区别?

5、ArrayList是如何扩容的?

6、ArrayList是线程安全的么

7、如何让 ArrayList 变成线程安全的?

8、Set集合是如何保证元素不可重复的

9、HashMap底层是如何实现的?

10、为什么Jdk8之后(包含Jdk8),HashMap要使用红黑树,为什么不继续使用链表?

11、HashMap的链表在长度为几时会转成红黑树?

12、为什么长度为6的时候转回来?

13、什么是哈希冲突?如何解决哈希冲突?

14、HashMap的查询流程?(get操作)

15、HashMap的新增流程?(put操作)

16、HashMap的删除流程?(remove操作)

17、HashMap在什么情况下会触发扩容操作?

18、HashMap是如何扩容的?

19、HashMap的默认初始容量是多少?

20、为什么HashMap的默认负载因子设置成0.75?

21、HashMap是线程安全的么?

22、HashMap为什么会死循环?

23、HashMap为什么会数据覆盖?

24、如何保证HashMap线程安全?

25、HashMap、Hashtable和ConcurrentHashMap的区别?

26、为什么 HashMap 允许插入 null键 和 null 值?

27、什么是二义性问题? 

28、为什么Hashtable和ConcurrentHashMap不允许插入nuI键和nuI值?

29、Hashtable是如何保证线程安全的?

30、ConcurrentHashMap是如何保证线程安全的?

31、HashSet简介


1、Java的集合类有哪些?

在Java中,常用的集合主要分为五个大类,分别是列表(List)集合(Set)队列(Queue)栈(Stack)字典(Map)

其中,前四种结构都是单一元素的集合(每个位置放一个元素),而Map则是以Key-Value键值对的形式使用的

  1. 列表(List):有序集合,可以包含重复元素。常见实现类有 ArrayList、LinkedList、Vector 等。
  2. 集合(Set):无序集合,不允许包含重复元素。常见实现类有 HashSet、TreeSet 等。
  3. 队列(Queue)::先进先出的数据结构。常见实现类有 BlockingQueue、PriorityQueue 等.
  4. 栈(Stack):先进后出的数据结构。
  5. 字典(Map):键值对的集合,每个键只能对应一个值。常见实现类有 HashMap、TreeMap、LinkedHashMap 等。

各个集合的特点在上面用加粗字号显示了,依此口述即可

从继承关系上讲,List,Set,Queue都是Collection的子接口,Map属于一个单独的分类

可以了解一下:Collection又继承了Iterable接口,所以我们经常可以看到,集合类使用迭代器进行遍历

口述话:(阅读即可,不要背,太口语化了)

在大部分的实际使用中,最常用的集合接口包括 List、Set、Map 等。

其中,List 接口表示一个有序的集合,可以包含重复的元素,最常用的实现类有ArrayList,可以说在代码中,只要用到集合类,它一定是脑子中最先想到的。当然,既然您都提问我八股文了,那么理论上最常用的也有LinkedList(链表),毕竟增删的话,链表确实比偏数组的ArrayList更快。其次,需要注意的是,栈,stack也是List的子接口,但是队列,Queue不是

Set 接口表示一个不重复的集合,只有当我们对“不重复”三个字有需求时才会用到它,否则还是优先使用list

Map 接口表示一组键值对的映射关系。这个不比list用的少,甚至无处不在。毕竟其他的数据结构都是一个萝卜一个坑,只有他是键值对存储的,他的查找、插入和删除都是非常迅速的,我们也需要用到它键值对的特性。我举一个常见的使用该场景吧,用户的会话管理,这个时候,我们就可以使用Map来存储用户的会话信息,也就是Session,每个用户的会话信息可以通过唯一的会话ID来索引,说白了,就是这样的结构:<key, Value>: <会话ID, Session>

2、集合和数组的区别?

与其说是集合和数组的区别,不如说是ArrayList和数组的区别

在 Java 中,数组属于一种特殊的数据结构。它是一种固定大小的用于存储相同类型的多个元素的一种容器。数组在内存中是一个连续的存储区域,可以通过索引来访问其中的某个元素。

int[] numbers = {5, 3, 7, 2, 1};

而集合,是一种用于存储和操作一组对象的数据结构。它们通过一组接口和类来实现,提供了丰富的方法和功能使得对数据的处理更加方便和高效。

List<Integer> numbers = new ArrayList<>();

从严格意义上来讲,数组和集合都是用于存储数据的容器,但数组不属于集合。其实在我看来,二者在实际使用上的最大区别就两点

  1. 数组无法存储泛型,这就导致数组能存储的数据结构很有限,无法存储我们自己的实体类
  2. 数组在初始化时需要指定大小,后续改变存储大小就很困难了,而集合我们不需要考虑这点,它会动态扩展

注:其他list的实现类和数组差别太大了,比较意义不大,咱很多时候,说集合和数组的区别,其实默认就是比较ArrayList和数组的区别

3、ArrayList和LinkedList的区别

说白了,更像是数组和链表的区别,ArrayList是一个可以改变大小的数组,LinkedList是一个双向链表

注:为了防止有些同学困惑,我还是在这里说一下吧,严格意义上,ArrayList是集合,数组是类似int[]这样的,你要专门比较二者的话,你肯定不能说ArrayList是数组,但在我们不讨论严格意义上的数组时,我们是经常会把ArrayList说成数组的,这也是所有开发者的习惯了

ArrayList和LinkedList都是数据的集合,区别如下:

1、底层数据结构实现不同:

  • ArrayList底层使用数组实现,是一块连续的内存区域,通过一个可调整大小的数组来存储元素
  • LinkedList 底层使用双向链表实现,可以由不连续的内存区域实现,它通过链表节点来连接元素,每个元素都会存储上一个元素和下一个元素的地址

2、插入和删除的效率不同:

  • ArrayList 对于插入和删除操作的性能相对较低,因为需要进行元素的移动和数组的重新分配,尤其是在ArrayList 列表最前面插入和删除时,效率最慢。
  • LinkedList 对于插入和删除操作会比 ArrayList 更好,因为它只需要修改相邻节点的指针即可。

3、随机访问效率不同:

  • ArrayList 对于随机访问(根据索引获取元素)具有更好的性能,因为可以通过索引直接计算元素在数组中的位置,时间复杂度为 O(1)
  • LinkedList 对于随机访问的性能较差,需要通过链表节点一个个遍历找到对应的索引位置,时间复杂度为O(n)

4、内存要求和占用空间大小不同:

  • ArrayList 在内存中需要连续的存储空间,因此在存储大量数据时,需要有大块的连续内存空间,所以对内存要求较高(不能有太多的内存碎片)。
  • LinkedList 不要求有连续的内存空间,它的链表是逻辑的先后顺序,每个元素用额外的空间来存储指向前和后的节点指针,所以,LinkedList 相对而言会占用更多的内存空间。

因此,在多查的场景下考虑使用 ArrayList,而在插入和删除比较多的场景下考虑使用 LinkedList

4、ArrayList和Vector有什么区别?

ArrayList 和 Vector 都是 List 的子类,都实现了 List 接口,如下图所示:

首先,需要明白的是,尽管从上图看,Stack继承Vector,但是Vector已经不是接口了,是一个具体的类

二者的区别主要有以下几点:

  1. 线程安全不同:Vector 是线程安全的,而 ArrayList 是非线程安全的。在 Vector中,每个方法都使用了synchronized 关键字进行同步,以确保线程安全。
  2. 扩容策略不同:Vector 和 ArrayList 都是可以动态扩容的。但是,当动态的长度不同,Vector 会自动增长当前的容量一倍,也就是扩容 100%,而 ArrayList 只会增长当前容量的 50%。
  3. 性能不同:ArrayList 由于不具备线程安全的机制,所以在单线程环境下通常有更好的性能。而 Vector 由于是线程安全的,使用 synchronized 修饰,所以会带来额外的加锁和释放锁的性能开销,因此在单线程环境下Vector 性能不如 ArrayList。

5、ArrayList是如何扩容的?

首先,我们要明白ArrayList是基于数组的,我们都知道,申请数组的时候,只能申请一个定长的数组,那么List是如何通过数组扩容的呢?

ArrayList都是在新增元素时发现容量不足而触发扩容操作的

ArrayList的扩容分为以下几步:

  1. 检查新增元素后是否会超过数组的容量,如果超过,则进行下一步扩容
  2. 设置新的容量为老容量的1.5倍,容量最多不超过2^31-1
  3. 之后,申请一个容量为1.5倍的数组,并将元素复制到新数组中,扩容完成。
  4. 最后将原本要添加的那个新元素添加到数组中

6、ArrayList是线程安全的么

不是!

多线程环境下,如果多个线程同时对同一个 ArrayList 进行添加、删除或修改操作,可能会导致数据不一致或发生异常。这是因为,ArrayList 在内部实现时,并没有添加任何线程同步的机制,所以如果有多个线程同时对ArrayList 进行修改时,就会导致线程不安全的问题发生。

例如,添加操作和修改操作同时执行,那么它们的执行情况可能是这样的:

  1. 首先,先执行添加操作,而添加时发现 ArrayList 需要进行扩容,所以此时就先执行了扩容操作。
  2. 扩容操作执行一半之后,当前线程 CPU 时间片用完了,停止执行。
  3. 修改线程开始执行,于是将 ArrayList 已经扩容的这部分旧数据进行修改,修改线程执行完成。(问题已经出现了,因为修改的是原数组,也就是老数组)
  4. 扩容操作继续执行,将后半一半数组进行扩容。
  5. 将原对象的引用更换到新数组上。

此时就会发现,修改线程的修改操作失效了,因为修改线程,修改的是老数组,而添加操作在扩容时,已经将旧数据复制到新数组了,所以此时的修改操作就丢失了,这就是线程安全问题。

7、如何让 ArrayList 变成线程安全的?

想要让 ArrayList 变成线程安全的,也就是想要在多线程下使用 ArrayList 的方案有以下两类

  1. 加锁:在多线程下,对 ArrayList 进行非查询操作时,先加锁,可以使用 synchronized 或 Lock,让线程排队执行,这样对于 ArrayList 的操作就变成单线程了,这样 ArrayList 就是线程安全的了。
  2. 更换同类型线程安全的容器:在多线程下可以将 ArrayList 更换为线程安全的 CopyOnWriteArrayList或Vector,这样也不会有线程安全问题了。

这里简单说一下,CopyOnWriteArrayList和Vector分别靠什么实现线程安全的(防止被面试官提问)

  1. CopyOnWriteArrayList是使用 “写时复制” 的机制来实现线程安全的。具体来说,每当有写操作(如add、remove)发生时,CopyOnWriteArrayList 会创建一个新的数组副本,并在新
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dream_ready

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值