Java集合常见面试题总结(上)
集合概述
Java 集合概览
Java 集合,也叫作容器,主要是由两大接口派生而来:一个是 Collection
接口,主要用于存放单一元素;另一个是 Map
接口,主要用于存放键值对。对于Collection
接口,下面又有三个主要的子接口:List
、Set
、 Queue
。
Java 集合框架如下图所示:
Java 集合框架概览
注:图中只列举了主要的继承派生关系,并没有列举所有关系。比方省略了AbstractList
, NavigableSet
等抽象类以及其他的一些辅助类,如想深入了解,可自行查看源码。
如何选用集合?
- 我们只需要存放 单个元素值时,就选择实现
Collection
接口的集合。需要保证 元素 唯一就选择实现Set
接口的下面集合比如HashSet
,如果 要保证有序,就选择实现List
接口的比如ArrayList。
- 我们需要 存放键值对,就选用
Map
接口下的集合。如果需要 有序 就选择TreeMap
,需要 保证 线程安全 就选用ConcurrentHashMap,其他情况的话
就选择HashMap。
使用集合的好处?
当我们需要存储一组类型相同的数据时,数组是最常用且最基本的容器之一。但是,使用数组存储对象存在一些不足之处,因为在实际开发中,存储的数据类型多种多样且数量不确定。这时,Java 集合就派上用场了。与数组相比,Java 集合提供了更灵活、更有效的方法来存储多个数据对象。Java 集合框架中的各种集合类和接口可以存储不同类型和数量的对象,同时还具有多样化的操作方式。相较于数组,Java 集合的优势在于它们的大小可变、支持泛型、具有内建算法等。总的来说,Java 集合提高了数据的存储和处理灵活性,可以更好地适应现代软件开发中多样化的数据需求,并支持高质量的代码编写。
List
ArrayList 和 (数组)的区别?
- 数组 被创建之后,长度固定,不能改变;
ArrayList 可以
动态地扩容 长度。 - 数组 不支持泛型,
ArrayList
可以使用 泛型<T> ,来确保类型安全。 - 数组 只能通过 下标 访问元素,不提供 api 方法;
ArrayList
提供了丰富的 API 方法,比如add()
、remove() 增删元素的方法
。
下面是二者使用的简单对比:
Array
:
// 初始化一个 String 类型的数组
String[] stringArr = new String[]{"hello", "world", "!"};
// 修改数组元素的值
stringArr[0] = "goodbye";
System.out.println(Arrays.toString(stringArr));// [goodbye, world, !]
// 删除数组中的元素,需要手动移动后面的元素
for (int i = 0; i < stringArr.length - 1; i++) {
stringArr[i] = stringArr[i + 1];
}
stringArr[stringArr.length - 1] = null;
System.out.println(Arrays.toString(stringArr));// [world, !, null]
ArrayList
:
// 初始化一个 String 类型的 ArrayList
ArrayList<String> stringList = new ArrayList<>(Arrays.asList("hello", "world", "!"));
// 添加元素到 ArrayList 中
stringList.add("goodbye");
System.out.println(stringList);// [hello, world, !, goodbye]
// 修改 ArrayList 中的元素
stringList.set(0, "hi");
System.out.println(stringList);// [hi, world, !, goodbye]
// 删除 ArrayList 中的元素
stringList.remove(0);
System.out.println(stringList); // [world, !, goodbye]
ArrayList 和 Vector 的区别?(了解即可)
ArrayList
是List
的主要实现类,底层使用Object[]
存储,适用于频繁的查找工作,线程不安全 。Vector
是List
的古老实现类,底层使用Object[]
存储,线程安全。
Vector 和 Stack 的区别?(了解即可)
Vector
和Stack
两者都是线程安全的,都是使用synchronized
关键字进行同步处理。Stack
继承自Vector
,是一个后进先出的栈,而Vector
是一个列表。
随着 Java 并发编程的发展,Vector
和 Stack
已经被淘汰,推荐使用并发集合类(例如 ConcurrentHashMap
、CopyOnWriteArrayList
等)或者手动实现线程安全的方法来提供安全的多线程操作支持。
ArrayList 与 LinkedList 区别?
- 是否保证线程安全:
ArrayList
和LinkedList
都是线程不安全的 - 底层数据结构:
ArrayList
底层使用的是Object
数组,查询速度快,增删改 慢;LinkedList
底层使用的是 双向链表,查询速度慢 ,增删改 快。 - 插入元素的时间复杂度:
ArrayList
,头部插入,由于需要将所有元素都依次向后移动一个位置,因此时间复杂度是 O(n);尾部插入,当ArrayList
的容量未达到阈值时,只需要在数组末尾添加一个元素即可,时间复杂度是 O(1)。当容量已达到阈值时,就需要扩容,将原数组复制到新的更大的数组中,时间复杂度O(n);指定位置插入,需要将目标位置之后的所有元素都向后移动一个位置,然后再把新元素放入指定位置,因此时间复杂度为 O(n)。LinkedList
在 头尾 插入元素时,只需要修改头尾结点的指针就行,所以时间复杂度为 O(1);但是如果要在指定位置 i 插入元素时候,需要先遍历到指定位置i,再修改指定节点的指针,所以时间复杂度为 O(n)。
说一说 ArrayList 的扩容机制吧
具体具体看这篇文章,ArrayList 扩容机制
ArrayList 可以添加 null 值吗?
ArrayList
中可以存储任何类型的对象,包括 null
值。不过,不建议向ArrayList
中添加 null
值, null
值无意义,会让代码难以维护比如忘记做判空处理就会导致空指针异常。
示例代码:
ArrayList<String> listOfStrings = new ArrayList<>();
listOfStrings.add(null);
listOfStrings.add("java");
System.out.println(listOfStrings);
输出:
[null, java]
LinkedList 为什么不能实现 RandomAccess 接口?
由于 LinkedList
底层数据结构是 链表,内存地址 不连续,不支持随机快速访问,所以不能实现