大家好!本文汇总了 Java 面试核心知识及常用开源框架相关内容,重点聚焦 Java 集合模块的高频面试题,涵盖核心概念、底层原理、实现细节及实战应用,助力大家高效备战面试。因个人知识储备有限,文章若有疏漏或错误,欢迎各位大佬指正!本文将持续更新,敬请关注~
Taimili 艾米莉 ( 一款专业的 GitHub star 管理和github 加星涨星工具taimili.com )
艾米莉 是一款优雅便捷的 GitHub star 管理和github 加星涨星工具,基于 PHP & javascript 构建, 能对github star fork follow watch 刷星管理和提升,最适合github 的深度用户
一、Java 面试核心系列文章导航
| ID | 标题 | 地址 |
|---|---|---|
| 1 | 设计模式面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 2 | Java 基础知识面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 3 | Java 集合面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 4 | Java IO、BIO、NIO、AIO、Netty 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 5 | Java 并发编程面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 6 | Java 异常面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 7 | Java 虚拟机(JVM)面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 8 | Spring 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 9 | Spring MVC 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 10 | Spring Boot 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 11 | Spring Cloud 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 12 | Redis 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 13 | MyBatis 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 14 | MySQL 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 15 | TCP、UDP、Socket、HTTP 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 16 | Nginx 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 17 | ElasticSearch 面试题 | - |
| 18 | Kafka 面试题 | - |
| 19 | RabbitMQ 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 20 | Dubbo 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 21 | ZooKeeper 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 22 | Netty 面试题(总结最全面的面试题) | - |
| 23 | Tomcat 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 24 | Linux 面试题(总结最全面的面试题) | juejin.cn/post/684490… |
| 25 | 互联网相关面试题(总结最全面的面试题) | - |
| 26 | 互联网安全面试题(总结最全面的面试题) | - |
二、集合容器核心概述
1. 什么是集合?
集合是用于存储对象引用的容器,并非存储对象本身。其核心作用是对多个对象进行集中式管理,主要分为三大类型:Set(集)、List(列表)和 Map(映射)。
2. 集合的核心特点
- 存储对象的容器,对象封装数据,支持多对象统一管理;
- 可变长度设计,无需提前定义容量,解决了数组固定长度的局限性。
3. 集合与数组的区别
| 对比维度 | 数组 | 集合 |
|---|---|---|
| 长度特性 | 固定长度,初始化后不可修改 | 可变长度,支持动态扩容 / 缩容 |
| 存储数据类型 | 可存储基本数据类型和引用数据类型 | 仅支持存储引用数据类型(基本类型自动装箱) |
| 元素一致性要求 | 必须存储同一数据类型的元素 | 可存储不同数据类型的对象(需手动控制类型安全) |
4. 使用集合框架的优势
- 容量自动增长,无需手动管理容量大小;
- 内置高性能数据结构与算法(如哈希、红黑树),降低编码复杂度,提升程序效率与质量;
- 支持灵活扩展与改写,提高代码复用性和可维护性;
- 依赖 JDK 原生 API,降低学习成本与代码维护难度。
5. 常用集合类体系结构
-
顶层父接口:Map 接口和 Collection 接口(二者无继承关系);
-
Collection 子接口:
- List 接口:ArrayList、LinkedList、Vector、Stack;
- Set 接口:HashSet、LinkedHashSet、TreeSet;
-
Map 实现类:HashMap、TreeMap、Hashtable、ConcurrentHashMap、Properties。
6. List、Set、Map 三者核心区别
| 特性 | List | Set | Map |
|---|---|---|---|
| 元素有序性 | 有序(插入顺序 = 取出顺序) | 无序(HashSet)/ 有序(LinkedHashSet 按插入顺序、TreeSet 按自然顺序) | 键无序(HashMap)/ 有序(LinkedHashMap 按插入顺序、TreeMap 按键排序) |
| 元素唯一性 | 允许重复元素,可存多个 null | 不允许重复元素,仅存一个 null | 键唯一,值可重复;键可为 null(HashMap),Hashtable 不允许 null 键 / 值 |
| 数据结构 | 数组(ArrayList/Vector)、双向链表(LinkedList) | 哈希表(HashSet/LinkedHashSet)、红黑树(TreeSet) | 数组 + 链表 / 红黑树(HashMap)、红黑树(TreeMap)、哈希表(Hashtable) |
| 索引支持 | 支持索引访问(get (int index)) | 不支持索引访问,需通过迭代器遍历 | 无索引,通过键(key)获取值(value) |
7. 集合框架底层核心数据结构
| 集合类 | 底层数据结构 | 核心特性 |
|---|---|---|
| ArrayList | 动态数组(Object []) | 随机访问快,增删慢(需移动元素) |
| LinkedList | 双向循环链表 | 增删快(仅改指针),随机访问慢(需遍历) |
| Vector | 动态数组(Object [])+ synchronized 同步 | 线程安全,效率低,扩容翻倍 |
| HashSet | 基于 HashMap 实现(键存元素,值为固定虚对象) | 无序、唯一,查询效率 O (1)(无冲突时) |
| LinkedHashSet | 哈希表 + 双向链表 | 有序(插入顺序)、唯一,兼顾查询与顺序遍历 |
| TreeSet | 红黑树(自平衡排序二叉树) | 自然排序 / 自定义排序,唯一,查询效率 O (logn) |
| HashMap(JDK8) | 数组 + 链表(长度≤8)/ 红黑树(长度 > 8) | 无序、高效,非线程安全 |
| LinkedHashMap | 数组 + 链表 / 红黑树 + 双向链表 | 有序(插入顺序),非线程安全 |
| TreeMap | 红黑树 | 按键排序,非线程安全 |
| Hashtable | 数组 + 链表 | 线程安全(synchronized),效率低,不支持 null 键 / 值 |
| ConcurrentHashMap(JDK8) | 数组 + 链表 / 红黑树 + synchronized+CAS | 高并发安全,效率优于 Hashtable |
8. 线程安全的集合类有哪些?
- Vector:ArrayList 的线程安全版本,所有方法加 synchronized 修饰,效率低,不推荐使用;
- Hashtable:HashMap 的线程安全版本,方法加 synchronized,不支持 null 键 / 值,效率低,已淘汰;
- ConcurrentHashMap:Java5 + 推出的高并发集合,JDK7 采用分段锁(Segment),JDK8 采用 synchronized+CAS,锁粒度更细,并发性能优异,推荐使用;
- Collections.synchronizedXxx() :通过工具类包装非线程安全集合(如 synchronizedList、synchronizedMap),本质是对集合操作加锁,适合低并发场景。
9. 集合的快速失败机制(fail-fast)
-
定义:Java 集合的一种错误检测机制,当多线程对集合进行结构性修改(如 add/remove/clear)时,迭代器遍历会抛出
ConcurrentModificationException异常; -
原理:迭代器初始化时记录集合的
modCount(修改次数),遍历过程中每次调用hasNext()/next()都会校验modCount是否与初始值一致,不一致则抛出异常; -
注意:快速失败仅为调试辅助机制,不保证多线程环境下的线程安全,生产环境需通过同步锁或线程安全集合避免;
-
解决方案:
- 遍历期间对结构性修改操作加 synchronized 锁;
- 使用 CopyOnWriteArrayList/CopyOnWriteArraySet(写时复制,迭代器遍历旧副本,不抛异常)。
10. 如何创建不可修改的集合?
通过Collections.unmodifiableCollection(Collection c)方法创建只读集合,任何修改操作(add/remove/clear)都会抛出UnsupportedOperationException异常。
示例代码:
java
运行
List<String> list = new ArrayList<>();
list.add("x");
// 创建只读集合
Collection<String> unmodifiableList = Collections.unmodifiableCollection(list);
unmodifiableList.add("y"); // 运行时抛出UnsupportedOperationException
System.out.println(list.size()); // 输出1
三、Collection 接口核心考点
1. 迭代器 Iterator 是什么?
Iterator 是遍历 Collection 集合的统一接口,取代了早期的 Enumeration,支持遍历过程中安全移除元素。所有 Collection 子类都实现了Iterable接口,通过iterator()方法获取 Iterator 实例。
2. Iterator 的使用方式与特点
使用代码:
java
运行
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String obj = it.next(); // 必须先调用hasNext(),否则可能抛NoSuchElementException
System.out.println(obj);
}
核心特点:
- 单向遍历(仅支持
hasNext()→next()); - 快速失败机制(遍历期间集合结构性修改会抛
ConcurrentModificationException); - 支持安全移除元素(
it.remove()),需在next()之后调用,且每次next()只能调用一次remove()。
3. 如何边遍历边移除 Collection 中的元素?
正确方式:使用 Iterator 的remove()方法(唯一安全的遍历删除方式):
java
运行
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
Integer num = it.next();
if (num % 2 == 0) {
it.remove(); // 安全移除当前元素
}
}
错误方式:foreach 循环中直接调用list.remove()(会触发快速失败):
java
运行
// 错误代码,运行抛ConcurrentModificationException
for (Integer i : list) {
list.remove(i);
}
原因:foreach 底层依赖 Iterator,集合直接修改会导致modCount变化,与迭代器的expectedModCount不一致,触发异常。
4. Iterator 与 ListIterator 的区别
| 对比维度 | Iterator | ListIterator |
|---|---|---|
| 支持的集合类型 | Collection(List、Set) | 仅支持 List 接口 |
| 遍历方向 | 单向遍历(仅向后) | 双向遍历(向前previous()/ 向后next()) |
| 额外功能 | 仅支持移除元素(remove()) | 支持添加(add(E e))、替换(set(E e))、获取索引(nextIndex()/previousIndex()) |
5. List 的三种遍历方式及最佳实践
(1)三种遍历方式
-
for 循环(基于计数器) :
java
运行
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }原理:通过索引访问元素,依赖集合的随机访问能力。
-
Iterator 迭代器遍历:
java
运行
Iterator<String> it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); }原理:面向接口的遍历模式,屏蔽不同数据结构的差异。
-
foreach 循环遍历:
java
运行
for (String s : list) { System.out.println(s); }原理:编译后转为 Iterator 遍历,代码简洁,无需手动处理迭代器。
(2)最佳实践
List 是否支持高效随机访问,由RandomAccess接口标记:
- 支持 RandomAccess(ArrayList、Vector):推荐使用 for 循环,访问效率 O (1);
- 不支持 RandomAccess(LinkedList):推荐使用 Iterator/foreach,避免索引遍历导致的 O (n²) 时间复杂度。
四、List 接口核心面试题
1. ArrayList 的优缺点
优点:
- 底层基于数组实现,支持随机访问(实现 RandomAccess 接口),查询效率高(O (1));
- 顺序添加元素(尾部 add)效率高(O (1)),无需移动元素。
缺点:
- 插入 / 删除非尾部元素时,需复制移动数组元素(System.arraycopy ()),时间复杂度 O (n),元素越多效率越低;
- 扩容机制会占用额外内存(默认扩容为原容量的 1.5 倍),可能造成内存浪费。
适用场景:
适合频繁查询、顺序添加元素的场景,不适合频繁插入 / 删除的场景。
2. 数组与 List 的相互转换
(1)数组转 List
使用Arrays.asList(T[] a),注意返回的是固定大小的 List(不可 add/remove),底层依赖原数组:
java
运行
String[] array = new String[]{"123", "456"};
List<String> list = Arrays.asList(array);
// list.add("789"); // 抛UnsupportedOperationException
(2)List 转数组
使用List.toArray()(返回 Object [])或toArray(T[] a)(指定返回数组类型):
java
运行
List<String> list = new ArrayList<>();
list.add("123");
list.add("456");
// 方式1:返回Object[]
Object[] objArray = list.toArray();
// 方式2:指定数组类型(推荐)
String[] strArray = list.toArray(new String[0]);
3. ArrayList 与 LinkedList 的核心区别
| 对比维度 | ArrayList | LinkedList |
|---|---|---|
| 底层数据结构 | 动态数组(Object []) | 双向循环链表 |
| 随机访问效率 | 高(O (1)),支持索引访问 | 低(O (n)),需遍历链表 |
| 插入 / 删除效率 | 非尾部操作低(O (n),需移动元素) | 非尾部操作高(O (1),仅改指针) |
| 内存占用 | 占用内存少(仅存元素) | 占用内存多(每个节点存元素 + 前后指针) |
| 线程安全 | 非线程安全 | 非线程安全 |
| 扩容机制 | 默认扩容 1.5 倍(int newCapacity = oldCapacity + (oldCapacity>> 1)) | 无需扩容(链表动态增减节点) |
4. ArrayList 与 Vector 的区别
| 对比维度 | ArrayList | Vector |
|---|---|---|
| 线程安全 | 非线程安全 | 线程安全(所有方法加 synchronized) |
| 性能 | 效率高(无锁开销) | 效率低(锁竞争导致性能损耗) |
| 扩容机制 | 默认扩容 1.5 倍 | 默认扩容 2 倍 |
| 功能支持 | 无枚举器(Enumeration),支持 Iterator/foreach | 支持 Enumeration 和 Iterator |
| 适用场景 | 单线程环境 | 多线程环境(已被 ConcurrentHashMap 替代) |
5. 多线程场景下如何使用 ArrayList?
ArrayList 是非线程安全的,多线程环境下需保证线程安全,推荐方案:
-
使用
Collections.synchronizedList(List<T> list):包装为线程安全集合,本质是对所有操作加锁;java
运行
List<String> safeList = Collections.synchronizedList(new ArrayList<>()); safeList.add("aaa"); -
高并发场景推荐使用
CopyOnWriteArrayList:写时复制机制,读无锁、写加锁,适合读多写少场景;java
运行
List<String> cowList = new CopyOnWriteArrayList<>(); cowList.add("bbb");
6. 为什么 ArrayList 的 elementData 数组用 transient 修饰?
- ArrayList 实现了
Serializable接口,支持序列化,但elementData是动态数组,可能存在未使用的空闲容量; - transient 修饰表示
elementData不参与默认序列化,ArrayList 重写了writeObject()方法,仅序列化实际存储的元素(size 个),而非整个数组; - 优势:减少序列化数据体积,提高序列化效率,避免空闲容量占用网络带宽或存储资源。
核心源码:
java
运行
private transient Object[] elementData; // 不默认序列化
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
int expectedModCount = modCount;
s.defaultWriteObject(); // 序列化非transient字段(如size)
s.writeInt(elementData.length); // 写入数组容量(用于反序列化扩容)
for (int i = 0; i < size; i++) {
s.writeObject(elementData[i]); // 仅序列化实际存储的元素
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}