Java面试题之Java集合框架篇(Java容器篇),29道Java集合框架八股文(1.4万字67张手绘图),面渣逆袭必看👍
前言
14554 字 67 张手绘图,详解 29 道 Java 集合框架面试高频题(让天下没有难背的八股),面渣背会这些 Java 容器八股文,这次吊打面试官,我觉得稳了(手动 dog)。整理:沉默王二,戳转载链接,作者:三分恶,戳原文链接。
亮白版本更适合拿出来打印,这也是很多学生党喜欢的方式,打印出来背诵的效率会更高。
2024 年 12 月 30 日开始着手第二版更新。
- 对于高频题,会标注在《Java 面试指南(付费)》中出现的位置,哪家公司,原题是什么,并且会加🌟,目录一目了然;如果你想节省时间的话,可以优先背诵这些题目,尽快做到知彼知己,百战不殆。
- 区分八股精华回答版本和原理底层解释,让大家知其然知其所以然,同时又能做到面试时的高效回答。
- 结合项目(技术派、pmhub)来组织语言,让面试官最大程度感受到你的诚意,而不是机械化的背诵。
- 修复第一版中出现的问题,包括球友们的私信反馈,网站留言区的评论,以及 GitHub 仓库中的 issue,让这份面试指南更加完善。
- 增加二哥编程星球的球友们拿到的一些 offer,对面渣逆袭的感谢,以及对简历修改的一些认可,以此来激励大家,给大家更多信心。
- 优化排版,增加手绘图,重新组织答案,使其更加口语化,从而更贴近面试官的预期。
由于 PDF 没办法自我更新,所以需要最新版的小伙伴,可以微信搜【沉默王二】,或者扫描/长按识别下面的二维码,关注二哥的公众号,回复【222】即可拉取最新版本。
当然了,请允许我的一点点私心,那就是星球的 PDF 版本会比公众号早一个月时间,毕竟星球用户都付费过了,我有必要让他们先享受到一点点福利。相信大家也都能理解,毕竟在线版是免费的,CDN、服务器、域名、OSS 等等都是需要成本的。
更别说我付出的时间和精力了,大家觉得有帮助还请给个口碑,让你身边的同事、同学都能受益到。
我把二哥的 Java 进阶之路、JVM 进阶之路、并发编程进阶之路,以及所有面渣逆袭的版本都放进来了,涵盖 Java基础、Java集合、Java并发、JVM、Spring、MyBatis、计算机网络、操作系统、MySQL、Redis、RocketMQ、分布式、微服务、设计模式、Linux 等 16 个大的主题,共有 40 多万字,2000+张手绘图,可以说是诚意满满。
展示一下暗黑版本的 PDF 吧,排版清晰,字体优雅,更加适合夜服,晚上看会更舒服一点。
引言
1.🌟说说有哪些常见的集合框架?
- 推荐阅读:二哥的 Java 进阶之路:Java 集合框架
- 推荐阅读:阻塞队列 BlockingQueue。
集合框架可以分为两条大的支线:
①、第一条支线 Collection,主要由 List、Set、Queue 组成:
- List 代表有序、可重复的集合,典型代表就是封装了动态数组的 ArrayList 和封装了链表的 LinkedList;
- Set 代表无序、不可重复的集合,典型代表就是 HashSet 和 TreeSet;
- Queue 代表队列,典型代表就是双端队列 ArrayDeque,以及优先级队列 PriorityQueue。
②、第二条支线 Map,代表键值对的集合,典型代表就是 HashMap。
另外一个回答版本:
①、Collection 接口:最基本的集合框架表示方式,提供了添加、删除、清空等基本操作,它主要有三个子接口:
List:一个有序的集合,可以包含重复的元素。实现类包括 ArrayList、LinkedList 等。Set:一个不包含重复元素的集合。实现类包括 HashSet、LinkedHashSet、TreeSet 等。Queue:一个用于保持元素队列的集合。实现类包括 PriorityQueue、ArrayDeque 等。
②、Map 接口:表示键值对的集合,一个键映射到一个值。键不能重复,每个键只能对应一个值。Map 接口的实现类包括 HashMap、LinkedHashMap、TreeMap 等。
集合框架有哪几个常用工具类?
集合框架位于 java.util 包下,提供了两个常用的工具类:
- Collections:提供了一些对集合进行排序、二分查找、同步的静态方法。
- Arrays:提供了一些对数组进行排序、打印、和 List 进行转换的静态方法。
简单介绍一下队列
Java 中的队列主要通过 Queue 接口和并发包下的 BlockingQueue 两个接口来实现。
优先级队列 PriorityQueue 是一个无界队列,它的元素按照自然顺序排序或者 Comparator 比较器进行排序。
双端队列 ArrayDeque 是一个基于数组的,可以在两端插入和删除元素的队列。
LinkedList 实现了 Queue 接口的子类 Deque,所以也可以当做双端队列来使用。
用过哪些集合类,它们的优劣?
我常用的集合类有 ArrayList、LinkedList、HashMap、LinkedHashMap。
ArrayList 可以看作是一个动态数组,可以在需要时动态扩容数组的容量,只不过需要复制元素到新的数组。优点是访问速度快,可以通过索引直接查找到元素。缺点是插入和删除元素可能需要移动或者复制元素。
LinkedList 是一个双向链表,适合频繁的插入和删除操作。优点是插入和删除元素的时候只需要改变节点的前后指针,缺点是访问元素时需要遍历链表。
HashMap 是一个基于哈希表的键值对集合。优点是可以根据键的哈希值快速查找到值,但有可能会发生哈希冲突,并且不保留键值对的插入顺序。
LinkedHashMap 在 HashMap 的基础上增加了一个双向链表来保持键值对的插入顺序。
队列和栈的区别了解吗?
队列是一种先进先出(FIFO, First-In-First-Out)的数据结构,第一个加入队列的元素会成为第一个被移除的元素,适用于需要按顺序处理任务的场景,比如消息队列、任务调度等。
栈是一种后进先出(LIFO, Last-In-First-Out)的数据结构,最后一个加入栈的元素会成为第一个被移除的元素,适用于需要回溯的场景,比如函数调用栈、浏览器历史记录等。
哪些是线程安全的容器?
像 Vector、Hashtable、ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue、ArrayBlockingQueue、LinkedBlockingQueue 都是线程安全的。
Collection 继承了哪些接口?
Collection 继承了 Iterable 接口,这意味着所有实现 Collection 接口的类都必须实现 iterator() 方法,之后就可以使用增强型 for 循环遍历集合中的元素了。
- Java 面试指南(付费)收录的用友金融一面原题:你了解哪些集合框架?
- Java 面试指南(付费)收录的华为一面原题:说下 Java 容器和 HashMap
- Java 面试指南(付费)收录的小米暑期实习同学 E 一面面试原题:你了解哪些集合?
- Java 面试指南(付费)收录的美团面经同学 16 暑期实习一面面试原题:知道哪些集合,讲讲 HashMap 和 TreeMap 的区别,讲讲两者应用场景的区别;讲一下有哪些队列,阻塞队列的阻塞是什么含义?
- Java 面试指南(付费)收录的农业银行面经同学 7 Java 后端面试原题:用过哪些集合类,它们的优劣
- Java 面试指南(付费)收录的华为 OD 面经同学 1 一面面试原题:队列和栈的区别了解吗?
- Java 面试指南(付费)收录的农业银行同学 1 面试原题:阻塞队列的实现方式
- Java 面试指南(付费)收录的小公司面经合集同学 1 Java 后端面试原题:Java 容器有哪些?List、Set 还有 Map 的区别?
- Java 面试指南(付费)收录的 360 面经同学 3 Java 后端技术一面面试原题:java 有哪些集合
- Java 面试指南(付费)收录的华为面经同学 11 面试原题:java 中的集合类型?哪些是线程安全的?
- Java 面试指南(付费)收录的招商银行面经同学 6 招银网络科技面试原题:Java 集合有哪些?
- Java 面试指南(付费)收录的用友面试原题:集合容器能列举几个吗?
- Java 面试指南(付费)收录的比亚迪面经同学 12 Java 技术面试原题:java的集合介绍一下
- Java 面试指南(付费)收录的 OPPO 面经同学 1 面试原题:介绍Java的集合框架
- Java 面试指南(付费)收录的vivo 面经同学 10 技术一面面试原题:Java中的集合有哪些
List
2.🌟ArrayList 和 LinkedList 有什么区别?
推荐阅读:二哥的 Java 进阶之路:ArrayList 和 LinkedList
ArrayList 是基于数组实现的,LinkedList 是基于链表实现的。
ArrayList 和 LinkedList 的用途有什么不同?
多数情况下,ArrayList 更利于查找,LinkedList 更利于增删。
①、由于 ArrayList 是基于数组实现的,所以 get(int index) 可以直接通过数组下标获取,时间复杂度是 O(1);LinkedList 是基于链表实现的,get(int index) 需要遍历链表,时间复杂度是 O(n)。
当然,get(E element) 这种查找,两种集合都需要遍历通过 equals 比较获取元素,所以时间复杂度都是 O(n)。
②、ArrayList 如果增删的是数组的尾部,时间复杂度是 O(1);如果 add 的时候涉及到扩容,时间复杂度会上升到 O(n)。
但如果插入的是中间的位置,就需要把插入位置后的元素向前或者向后移动,甚至还有可能触发扩容,效率就会低很多,变成 O(n)。
LinkedList 因为是链表结构,插入和删除只需要改变前置节点、后置节点和插入节点的引用,因此不需要移动元素。
如果是在链表的头部插入或者删除,时间复杂度是 O(1);如果是在链表的中间插入或者删除,时间复杂度是 O(n),因为需要遍历链表找到插入位置;如果是在链表的尾部插入或者删除,时间复杂度是 O(1)。
ArrayList 和 LinkedList 是否支持随机访问?
①、ArrayList 是基于数组的,也实现了 RandomAccess 接口,所以它支持随机访问,可以通过下标直接获取元素。
②、LinkedList 是基于链表的,所以它没法根据下标直接获取元素,不支持随机访问。
ArrayList 和 LinkedList 内存占用有何不同?
ArrayList 是基于数组的,是一块连续的内存空间,所以它的内存占用是比较紧凑的;但如果涉及到扩容,就会重新分配内存,空间是原来的 1.5 倍。
LinkedList 是基于链表的,每个节点都有一个指向下一个节点和上一个节点的引用,于是每个节点占用的内存空间比 ArrayList 稍微大一点。
ArrayList 和 LinkedList 的使用场景有什么不同?
ArrayList 适用于:
- 随机访问频繁:需要频繁通过索引访问元素的场景。
- 读取操作远多于写入操作:如存储不经常改变的列表。
- 末尾添加元素:需要频繁在列表末尾添加元素的场景。
LinkedList 适用于:
- 在列表中间频繁插入和删除元素的场景。
- 顺序访问多于随机访问的场景。
- LinkedList 可以实现队列(FIFO)和栈(LIFO)。
链表和数组有什么区别?
- 数组在内存中占用的是一块连续的存储空间,因此我们可以通过数组下标快速访问任意元素。数组在创建时必须指定大小,一旦分配内存,数组的大小就固定了。
- 链表的元素存储在于内存中的任意位置,每个节点通过指针指向下一个节点。
- Java 面试指南(付费)收录的京东同学 10 后端实习一面的原题:ArrayList 和 LinkedList 的时间复杂度
- Java 面试指南(付费)收录的小米暑期实习同学 E 一面面试原题:你了解哪些集合?
- Java 面试指南(付费)收录的小米面经同学 F 面试原题:ArrayList和LinkedList的区别和使用场景
- Java 面试指南(付费)收录的比亚迪面经同学 12 Java 技术面试原题:数组和链表的区别
- Java 面试指南(付费)收录的快手同学 2 一面面试原题:ArrayList和LinkedList区别
- Java 面试指南(付费)收录的得物面经同学 9 面试题目原题:集合里面的arraylist和linkedlist的区别是什么?有何优缺点?
memo:2025 年 9 月 07 日修改至此,今天在帮球友修改简历时,收到反馈说,蚂蚁集团转正了。希望看到这里的你也能顺利通过面试,拿到心仪的 offer。
3.ArrayList 的扩容机制了解吗?
了解。当往 ArrayList 中添加元素时,会先检查是否需要扩容,如果当前容量+1 超过数组长度,就会进行扩容。
扩容后的新数组长度是原来的 1.5 倍,然后再把原数组的值拷贝到新数组中。
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
- Java 面试指南(付费)收录的联想面经同学 7 面试原题:Java 集合类介绍,挑一个讲原理。
4.ArrayList 怎么序列化的知道吗?
在 ArrayList 中,writeObject 方法被重写了,用于自定义序列化逻辑:只序列化有效数据,因为 elementData 数组的容量一般大于实际的元素数量,声明的时候也加了 transient 关键字。
为什么 ArrayList 不直接序列化元素数组呢?
出于效率的考虑,数组可能长度 100,但实际只用了 50,剩下的 50 没用到,也就不需要序列化。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// 将当前 ArrayList 的结构进行序列化
int expectedModCount = modCount;
s.defaultWriteObject(); // 序列化非 transient 字段
// 序列化数组的大小
s.writeInt(size);
// 序列化每个元素
for (int i = 0; i < size; i++) {
s.writeObject(elementData[i]);
}
// 检查是否在序列化期间发生了并发修改
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
5.快速失败fail-fast了解吗?
fail—fast 是 Java 集合的一种错误检测机制。
在用迭代器遍历集合对象时,如果线程 A 遍历过程中,线程 B 对集合对象的内容进行了修改,就会抛出 Concurrent Modification Exception。
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用 hashNext()/next()遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改 modCount 值刚好又设置为了 expectedmodCount 值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的 bug。
java.util 包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改),比如 ArrayList 类。
什么是安全失败(fail—safe)呢?
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了 Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent 包下的容器都是安全失败,可以在多线程下并发使用,并发修改,比如 CopyOnWriteArrayList 类。
6.有哪几种实现 ArrayList 线程安全的方法?
常用的有两种。
可以使用 Collections.synchronizedList() 方法,它可以返回一个线程安全的 List。
SynchronizedList list = Collections.synchronizedList(new ArrayList());
内部是通过 synchronized 关键字加锁来实现的。
也可以直接使用 CopyOnWriteArrayList,它是线程安全的 ArrayList,遵循写时复制的原则,每当对列表进行修改时,都会创建一个新副本,这个新副本会替换旧的列表,而对旧列表的所有读取操作仍然在原有的列表上进行。
CopyOnWriteArrayList list = new CopyOnWriteArrayList();
通俗的讲,CopyOnWrite 就是当我们往一个容器添加元素的时候,不直接往容器中添加,而是先复制出一个新的容器,然后在新的容器里添加元素,添加完之后,再将原容器的引用指向新的容器。多个线程在读的时候,不需要加锁,因为当前容器不会添加任何元素。这样就实现了线程安全。
ArrayList 和 Vector 的区别?
Vector 属于 JDK 1.0 时期的遗留类,不推荐使用,仍然保留着是因为 Java 希望向后兼容。
ArrayList 是在 JDK 1.2 时引入的,用于替代 Vector 作为主要的非同步动态数组实现。因为 Vector 所有的方法都使用了 synchronized 关键字进行同步,所以单线程环境下效率较低。
- Java 面试指南(付费)收录的招商银行面经同学 6 招银网络科技面试原题:线程不安全的集合变成线程安全的方法?
- Java 面试指南(付费)收录的 比亚迪面经同学2面试原题:ArrayList 和 vector 的区别
7.CopyOnWriteArrayList 了解多少?
CopyOnWriteArrayList 就是线程安全版本的 ArrayList。
CopyOnWrite——写时复制,已经明示了它的原理。
CopyOnWriteArrayList 采用了一种读写分离的并发策略。CopyOnWriteArrayList 容器允许并发读,读操作是无锁的。至于写操作,比如说向容器中添加一个元素,首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。
Map
Map 中最重要的就是 HashMap 了,面试基本被问出包浆了,一定要好好准备。
8.🌟能说一下 HashMap 的底层数据结构吗?
JDK 8 中 HashMap 的数据结构是数组+链表+红黑树。
数组用来存储键值对,每个键值对可以通过索引直接拿到,索引是通过对键的哈希值进行进一步的 hash() 处理得到的。
当多个键经过哈希处理后得到相同的索引时,需要通过链表来解决哈希冲突——将具有相同索引的键值对通过链表存储起来。
不过,链表过长时,查询效率会比较低,于是当链表的长度超过 8 时(且数组的长度大于 64),链表就会转换为红黑树。红黑树的查询效率是 O(logn),比链表的 O(n) 要快。
hash() 方法的目标是尽量减少哈希冲突,保证元素能够均匀地分布在数组的每个位置上。
static final int has...
回复