Java集合面试题上
大约 7 分钟
Java集合面试题上
1. List, Set, Queue, Map 四者的区别:
- List:
List是一个有序集合,允许元素重复,并且可以通过索引访问元素。它实现了RandomAccess接口,意味着它支持基于索引的快速访问,因此在频繁进行随机访问时具有优势。常见实现包括ArrayList和LinkedList。- 使用场景:适合需要频繁随机访问或按顺序处理元素的场景。
- Set:
Set是一个不允许元素重复的集合,元素的顺序通常不确定(对于HashSet来说)。TreeSet提供了按自然顺序或自定义排序的功能。LinkedHashSet保持插入顺序。- 使用场景:适合去重操作或需要保证无重复元素的场景。
- Queue:
Queue是一种遵循 FIFO(先进先出)原则的集合,用于在队列中按顺序处理元素。PriorityQueue根据元素的优先级而非插入顺序出队。- 使用场景:适用于任务调度、生产者消费者模式等需要顺序处理的场景。
- Map:
Map是一个键值对集合,其中每个键映射到一个唯一的值。HashMap提供了最快的查找速度,而TreeMap按键的自然顺序进行排序,LinkedHashMap保留插入顺序。- 使用场景:适用于需要通过键快速查找值的场景。
2. 集合框架底层数据结构总结:
- 数组(Array):
ArrayList和HashMap等常用集合类通常会使用数组作为底层数据结构。数组具有较高的访问性能(O(1)),但在插入和删除时需要移动元素,因此在动态扩展时性能会下降。 - 链表(LinkedList):
LinkedList使用链表作为底层结构,支持 O(1) 时间复杂度的插入和删除操作,尤其适合频繁进行插入和删除的场景。但随机访问性能较差(O(n))。 - 树(Tree):
TreeSet和TreeMap使用红黑树等平衡二叉树结构,保证元素有序,支持 O(log n) 的查找、插入和删除操作。 - 哈希表(Hash Table):
HashSet和HashMap使用哈希表实现,具有 O(1) 的平均查找时间,但在哈希冲突发生时,性能会退化到 O(n)。
3. 为什么要使用集合?
集合是 Java 中一种非常重要的存储和操作数据的方式,提供了高效且灵活的解决方案。相比数组,集合类提供了更多的功能(如动态扩容、元素查找、排序、去重等),而且是线程安全的实现(如 ConcurrentHashMap 和 BlockingQueue)支持并发操作。集合的核心优势包括:
- 动态大小:不像数组需要预先定义大小,集合可以动态增长。
- 丰富的API:集合类提供了多种操作方法,能够大大提高开发效率。
- 灵活的存储结构:根据不同需求(如排序、去重、快速查找等)选择合适的数据结构。
4. ArrayList 和 Array(数组)的区别:
- 性能差异:
Array是一个固定大小的结构,访问元素的时间复杂度是 O(1)。而ArrayList是一个动态数组,能够自动扩容,但扩容时需要复制元素,因此扩容操作的时间复杂度是 O(n)。 - 灵活性:
Array不能自动扩容,且不能添加或删除元素,而ArrayList提供了丰富的方法来操作元素(如.add()、.remove()等)。 - 存储类型:
Array可以存储基本数据类型和对象,而ArrayList只能存储对象(必须使用包装类来存储基本数据类型)。
5. ArrayList 和 Vector 的区别:
- 线程安全性:
Vector是线程安全的,但由于其每个操作都需要加锁,所以在多线程环境下性能较差。ArrayList不具备线程安全性,但它的性能优于Vector。 - 增长策略:
Vector每次扩容时增长为当前容量的 2 倍,而ArrayList扩容时通常为原来容量的 1.5 倍。 - 使用场景:在单线程环境中,优先选择
ArrayList,因为它更高效;如果需要线程安全,可以选择CopyOnWriteArrayList或使用Collections.synchronizedList()来同步ArrayList。
6. Vector 和 Stack 的区别:
- Vector 是一个动态数组,支持随时调整大小。
- Stack 继承自
Vector,提供了 LIFO(后进先出)操作,方法包括.push()、.pop()和.peek(),常用于实现深度优先搜索(DFS)等算法。
7. ArrayList 是否可以添加 null 值?
是的,ArrayList 可以包含 null 元素。这是因为 ArrayList 底层使用数组,而数组可以存储 null 值(当然这取决于元素类型是否允许 null)。
8. ArrayList 插入和删除元素的时间复杂度:
- 插入:如果是在尾部插入,时间复杂度是 O(1)(摊销时间复杂度)。但如果是在中间或开头插入,则需要移动元素,时间复杂度是 O(n)。
- 删除:删除元素时需要移动剩余元素,因此在最坏情况下,删除操作的时间复杂度是 O(n)。
9. LinkedList 插入和删除元素的时间复杂度:
- 插入/删除:在链表头部或尾部插入和删除的时间复杂度是 O(1),但如果需要在中间插入或删除,需要遍历链表,时间复杂度是 O(n)。
10. LinkedList 为什么不能实现 RandomAccess 接口?
LinkedList 使用链表结构存储数据,每个节点都通过指针连接到下一个节点,因此需要从头部或尾部开始逐步遍历链表来访问元素,这导致随机访问的时间复杂度是 O(n),所以 LinkedList 无法实现 RandomAccess 接口。
11. ArrayList 与 LinkedList 区别:
- ArrayList:基于动态数组实现,支持快速的随机访问,适合频繁读取元素的场景;但对于中间插入和删除操作较慢。
- LinkedList:基于双向链表实现,适合频繁插入和删除操作,但对于随机访问性能较差。
12. ArrayList 的扩容机制:
ArrayList 每次扩容时将数组的大小增加为原来容量的 1.5 倍。当需要增加元素时,ArrayList 会重新分配一个新的数组,并将旧数组中的元素复制到新数组中,这个过程需要 O(n) 的时间。
13. Comparable 和 Comparator 的区别:
- Comparable:定义了类的自然顺序,类必须实现
compareTo()方法。 - Comparator:定义了外部排序规则,通过实现
compare()方法来对不同的类进行排序。Comparator不要求修改类本身。
14. Set 的无序性和不可重复性含义:
- 无序性:元素没有明确的排列顺序。
- 不可重复性:相同的元素只能出现一次,集合会自动去重。
15. HashSet、LinkedHashSet 和 TreeSet 区别:
- HashSet:无序且不重复,查找性能较好。
- LinkedHashSet:有序(按插入顺序),不重复。
- TreeSet:按自然顺序排序,不重复,查找性能相对较差,适合有序集合操作。
16. Queue 与 Deque 的区别:
- Queue:只允许从一端插入元素(尾部),从另一端移除元素(头部),遵循 FIFO(先进先出)原则。
- Deque:双端队列,允许从两端插入和移除元素,支持更多灵活的操作
。
17. ArrayDeque 与 LinkedList 的区别:
- ArrayDeque:基于动态数组实现,性能优于
LinkedList,尤其在栈和队列操作中。 - LinkedList:基于链表实现,插入删除操作更灵活,但性能相对较差。
18. PriorityQueue:
PriorityQueue 是一种基于堆的数据结构,用于按照优先级处理元素。元素的优先级由自然顺序或自定义的 Comparator 决定,适合任务调度、事件处理等场景。
19. 什么是 BlockingQueue?
BlockingQueue 是一种线程安全的队列,当队列为空时,消费者线程会阻塞直到有元素可用;当队列满时,生产者线程会阻塞,直到有空间可以插入元素。常用于生产者消费者模式。
20. ArrayBlockingQueue 和 LinkedBlockingQueue 区别:
- ArrayBlockingQueue:使用固定大小的数组,适用于已知最大容量的场景。
- LinkedBlockingQueue:支持更大的队列容量,通常用于多线程并发处理时。
