JAVA:集合框架常见的面试题和答案

1、List接口的常见实现类有哪些?

答: 常见的List接口实现类包括:

  • ArrayList: 基于动态数组实现的List,支持快速随机访问。
  • LinkedList: 基于链表实现的List,支持快速的插入和删除操作。
  • Vector: 一个线程安全的动态数组,通常不建议使用,可以用ArrayList代替。
  • Stack: 继承自Vector,表示一个后进先出(LIFO)的堆栈。

2、ArrayList和LinkedList的区别是什么?

  • 数据结构实现:ArrayList :基于数组,便于按 index 访问,超过数组需要扩容,扩容成本较高。LinkedList:使用链表实现,无需扩容。
  • 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
  • 增加和删除效率:在非首尾的增删操作,LinkedList 要比ArrayList 效率要高,因为ArrayList 增删操作要影响数组内的其他数据的下标。
  • 内存空间占用:LinkedList 比 ArrayList 更占内存,因为LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
  • 线程安全:ArrayList 和 LinkList 都是不同步的,不保证线程安全。
  • 综合来说,需要频繁读取集合中的元素时,更推荐使用 Arrayist,而在增删操作较多时,更推荐使用 LinkedList。
  • LinkedList 的双向链表是链表的一种,它的每个数据结点中都有2 个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便的访问它的前驱结点和后继结点。

3、Map接口的常见实现类有哪些?

答: 常见的Map接口实现类包括:

  • HashMap: 基于哈希表实现的Map,提供了快速的查找性能。
  • TreeMap: 基于红黑树实现的有序Map,按键的自然顺序或者定制的顺序进行排序。
  • LinkedHashMap: 基于哈希表和双向链表实现的Map,可以按照插入顺序或者访问顺序进行遍历。
  • Hashtable: 一个线程安全的哈希表,通常不建议使用,可以用HashMap代替。
  • ConcurrentHashMap: 一个线程安全的哈希表,支持并发操作。

4、HashTable,HashMap,TreeMap区别?

  • HashTable线程同步,HashMap,TreeMap非线程同步。
  • HashTable,TreeMap不允许<键,值>有空值,HashMap允许<键,值>有空值。
  • HashTable中hash数组的默认大小是11,增加方式的old*2+1,HashMap中hash数组的默认大小是16,增长方式一定是2的指数倍。
  • TreeMap能够把它保存的记录根据键排序,默认是按升序排序,因此如果你的键需要有序,建议使用TreeMap代替HashMap。

5、加载因子(扩容因子)为何默认是0.75?

  • 在空间占用与查询时间之间取得了较好的权衡
  • 大于这个值,空间节省了,但是链表可能过长就会影响性能
  • 小于这个值,冲突减少了,但是扩容就会比较频繁,空间占用多并且扩容也会消耗性能

6、LinkedHashMap的应用,底层,原理

答:LinkedHashMap维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序(insert-order)或者是访问顺序,其中默认的迭代访问顺序就是插入顺序,即可以按插入的顺序遍历元素,这点和HashMap有很大的不同。

LRU算法可以用LinkedHashMap实现。

7、HashMap 的底层数据结构是怎样的 ?

JDK1.8 之前

  • JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是链表散列。
  • HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到hash 值,然后通过(n-1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。
  • 所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。

JDK1.8 之后

  • 当链表长度大于阈值(默认为 8)时,会首先调用 treeifyBin()方法。这个方法会根据HashMap 数组来决定是否转换为红黑树。只有当数组长度大于或者等于64的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是执行resize() 方法对数组扩容。

8、HashMap 的扩容机制是怎样的?

一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的2倍。HashMap 的容量是有上限的,必须小于 1<<30,即 1073741824。如果容量超出了这个数,则不再增长,且阈值会被设置为 Integer.MAX_VALUE。

JDK7 中的扩容机制

  • 空参数的构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数组。
  • 有参构造函数:根据参数确定容量、负载因子、阈值等。
  • 第一次 put 时会初始化数组,其容量变为不小于指定容量的2 的幂数,然后根据负载因子确定阈值。
  • 如果不是第一次扩容,则 新容量=旧容量 x 2 ,新阈值=新容量x 负载因子。JDK8 的扩容机制
  • 空参数的构造函数:实例化的 HashMap 默认内部数组是null,即没有实例化。第一次调用 put 方法时,则会开始第一次初始化扩容,长度为 16。
  • 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值(threshold)。第一次调用 put 方法时,会将阈值赋值给容量,然后让 阈值 = 容量 x 负载因子。
  • 如果不是第一次扩容,则容量变为原来的 2 倍,阈值也变为原来的2 倍。(容量和阈值都变为原来的 2 倍时,负载因子还是不变)。
    此外还有几个细节需要注意:
  • 首次 put 时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;
  • 不是首次 put,则不再初始化,直接存入数据,然后判断是否需要扩容;

9、ConcurrentHashMap 的存储结构是怎样的?

  • Java7 中 ConcurrnetHashMap 使用的分段锁,也就是每一个Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变,默认Segment的个数是 16 个。
  • Java8 中的 ConcurrnetHashMap 使用的 Synchronized 锁加CAS 的机制。结构也由Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了Node 数组+链表/红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。

10、用过 ConcurrentHashMap,讲一下他和HashTable 的不同之处?

  • HashTable 就是实现了 HashMap 加上了 synchronized,而ConcurrentHashMap底层采用分段的数组+链表实现,线程安全
  • ConcurrentHashMap 通过把整个 Map 分为 N 个 Segment,可以提供相同的线程安全,但是效率提升 N 倍,默认提升 16 倍。
  • 并且读操作不加锁,由于 HashEntry 的 value 变量是 volatile 的,也能保证读取到最新的值。
  • Hashtable 的 synchronized 是针对整张 Hash 表的,即每次锁住整张表让线程独占,ConcurrentHashMap 允许多个修改操作并发进行,其关键在于使用了锁分离技术
  • 扩容:段内扩容(段内元素超过该段对应 Entry 数组长度的75%触发扩容,不会对整个Map 进行扩容),插入前检测需不需要扩容,有效避免无效扩容

11、比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同

  • HashSet 是 Set 接口的主要实现类 ,HashSet 的底层是HashMap,线程不安全的,可以存储 null 值;
  • LinkedHashSet 是 HashSet 的子类,能够按照添加的顺序遍历;
  • TreeSet 底层使用红黑树,元素是有序的,排序的方式有自然排序和定制排序。

12、Comparable 和 Comparator 的区别?

  • comparable 接口实际上是出自 java.lang 包 它有一个 compareTo(Object obj)方法用来排序
  • comparator 接口实际上是出自 java.util 包它有一个 compare(Object obj1, Objectobj2)方法用来排序
    一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo()方法或compare()方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写 compareTo()方法和使用自制的Comparator 方法或者以两个 Comparator 来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的 Collections.sort()。

13、什么是哈希冲突?

答:当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。

14、CopyOnWriteArrayList与ArrayList、Vector的区别

答:CopyOnWriteArrayList与ArrayList、Vector有以下主要区别:

  • 线程安全性:CopyOnWriteArrayList是线程安全的,而ArrayList不是;Vector也是线程安全的,但它使用全局锁,导致性能较差。
  • 读写性能:CopyOnWriteArrayList具有较高的并发读性能,但写操作性能较差,因为每次写操作都需要复制一个新的副本。ArrayList具有较高的读写性能,但在多线程环境下可能出现线程安全问题。Vector的读写性能较差,因为它使用全局锁。
  • 内存占用:CopyOnWriteArrayList在写操作时需要复制一个新的副本,因此可能导致较高的内存占用。ArrayList和Vector的内存占用相对较低。
  • 实时性:CopyOnWriteArrayList的迭代器只能获取到写操作前的数据副本,因此在迭代过程中无法获取实时数据。ArrayList和Vector的迭代器可以获取实时数据,但在多线程环境下可能会导致线程安全问题。

15、如何将Map转换为List?

答: 可以使用ArrayList的构造函数或者addAll方法将Map转换为List。

使用构造函数:

复制代码
List<Map.Entry<K, V>> list = new ArrayList<>(map.entrySet());

使用addAll方法:

复制代码
List<Map.Entry<K, V>> list = new ArrayList<>();
list.addAll(map.entrySet());

16、LinkedHashSet和CopyOnWriteArraySet的区别

答:LinkedHashSet和CopyOnWriteArraySet是Java集合框架中不同的实现类,它们具有不同的特性和适用场景。

LinkedHashSet:

  • 内部实现:
    LinkedHashSet 是基于哈希表和双向链表实现的,它继承自HashSet,保留了元素插入的顺序。
  • 有序性:
    LinkedHashSet 保持了元素插入的顺序,因此可以按照插入顺序进行遍历。
  • 性能:
    由于它的底层实现依赖于哈希表,因此查找元素的速度非常快。
  • 线程安全:
    LinkedHashSet 是非线程安全的。
  • 适用场景:
    当你需要保留元素插入的顺序,并且不需要考虑线程安全时,可以使用LinkedHashSet。

CopyOnWriteArraySet:

  • 内部实现:
    CopyOnWriteArraySet 是基于CopyOnWriteArrayList实现的,它是一个线程安全的Set。
  • 线程安全:
    CopyOnWriteArraySet 是线程安全的,它的所有修改操作都是在一个新的副本上进行的,而不会影响原始集合。
  • 写时复制:
    当需要对集合进行修改时,CopyOnWriteArraySet 会先复制一个新的数组,然后在新的数组上进行修改,最后将新的数组替换原始数组。
  • 适用场景:
    当你需要在多线程环境下保证安全地对集合进行修改时,可以使用CopyOnWriteArraySet。它适用于读多写少的场景。

17、Java中Queue接口的常见实现类有哪些?

答: 常见的Queue接口实现类包括LinkedList、PriorityQueue和ArrayDeque。

18、LinkedList和ArrayDeque之间有什么区别?

  • LinkedList 是一个双向链表实现的队列,可以作为队列和双端队列使用。
  • ArrayDeque 是一个基于动态数组实现的双端队列,它的性能比LinkedList稍好,但不支持索引访问。

19、PriorityQueue是什么?它是如何工作的?

答: PriorityQueue 是一个优先级队列,它根据元素的优先级来确定出队顺序。默认情况下,PriorityQueue 是一个最小堆实现,可以通过提供一个自定义的Comparator来实现最大堆。

20、队列和栈有什么区别?

  • 队列是一种先进先出(FIFO)的数据结构,只允许在队列的一端插入元素,在另一端删除元素。
  • 栈是一种后进先出(LIFO)的数据结构,只允许在栈的一端插入和删除元素。

21、如何在多线程环境中使用BlockingQueue?

答案: 可以通过创建一个BlockingQueue实例,然后在生产者线程中使用put方法向队列中添加元素,在消费者线程中使用take方法获取元素。

复制代码
BlockingQueue<ElementType> queue = new LinkedBlockingQueue<>();
// 生产者线程
queue.put(element);
// 消费者线程
ElementType element = queue.take();

22、ConcurrentLinkedQueue和LinkedBlockingQueue有什么区别?

  • ConcurrentLinkedQueue 是一个基于非阻塞算法实现的非阻塞队列,它在高并发环境下表现良好。
  • LinkedBlockingQueue 是一个基于锁实现的阻塞队列,它可以在队列为空或者满时阻塞线程。

23、如何实现一个自定义的优先级队列?

答案: 可以使用PriorityQueue并提供一个自定义的比较器(Comparator)来实现一个自定义的优先级队列。

复制代码
PriorityQueue<Element> priorityQueue = new PriorityQueue<>(new MyComparator()

24、ArrayBlockingQueue和LinkedBlockingQueue的区别

答:ArrayBlockingQueue和LinkedBlockingQueue是Java中两种不同类型的阻塞队列实现,它们有一些重要的区别。

ArrayBlockingQueue:

  • 内部实现:
    ArrayBlockingQueue 是一个基于数组实现的有界阻塞队列,它按照先进先出(FIFO)的原则对元素进行排序。
  • 容量限制:
    ArrayBlockingQueue 在创建时需要指定一个固定的容量,即队列中可以存放的最大元素数量。
  • 阻塞特性:
    当队列已满时,ArrayBlockingQueue 会阻塞等待直到有空间可以放置新元素。
    当队列为空时,尝试从中获取元素的操作会被阻塞等待,直到队列中有新的元素。
  • 性能:
    由于底层是基于数组实现的,因此在性能上比LinkedBlockingQueue更高效,但它的容量是固定的。

LinkedBlockingQueue:

  • 内部实现:
    LinkedBlockingQueue 是一个基于链表实现的无界阻塞队列,它按照先进先出(FIFO)的原则对元素进行排序。
  • 容量限制:
    LinkedBlockingQueue 的容量理论上可以是无限大,因此它不会因为达到最大容量而阻塞。
  • 阻塞特性:
    当队列为空时,尝试从中获取元素的操作会被阻塞等待,直到队列中有新的元素。
    当队列已满时,LinkedBlockingQueue 会阻塞等待直到有空间可以放置新元素。
  • 性能:
    由于底层是基于链表实现的,相对于ArrayBlockingQueue来说,它在高并发场景下可能会略显低效。
相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔5 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说10 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json