大家好,我是栗筝i,从 2022 年 10 月份开始,我便开始致力于对 Java 技术栈进行全面而细致的梳理。这一过程,不仅是对我个人学习历程的回顾和总结,更是希望能够为各位提供一份参考。因此得到了很多读者的正面反馈。
而在 2023 年 10 月份开始,我将推出 Java 面试题/知识点系列内容,期望对大家有所助益,让我们一起提升。
今天与您分享的,是 Java 集合知识面试题系列的总结篇(中篇),我诚挚地希望它能为您带来启发,并在您的职业生涯中起到助益作用。衷心感谢每一位朋友的关注与支持。
1、Java集合面试题问题
1.1、JavaSet集合相关–特性&方法
1.2、JavaSet集合相关-具体实现
- 问题 23. 介绍一下 HashSet 的底层结构和相关原理
- 问题 24. 介绍一下 LinkedHashSet 的底层结构和相关原理
- 问题 25. 介绍一下 TreeSet 的底层结构和相关原理
- 问题 26. 请解释一下 Java 中的 EnumSet?
- 问题 27. 请解释一下 Java 中的 SortedSet?
- 问题 28. 请解释一下 Java 中的 NavigableSet
1.3、Java排序接口相关
1.4、Java集合并发相关
- 问题 31. 请解释一下 Java 中的 CopyOnWrite
- 问题 32. 请解释一下 Java 中的 CopyOnWriteArrayList
- 问题 33. 简述什么是 Fail Fast?
- 问题 34. 简述什么是 Fail Safe?
- 问题 35. 请解释一下 Java 中的 ConcurrentModificationException?
1.5、Java迭代器相关
- 问题 36. Java 中迭代器 Iterator 是什么?
- 问题 37. Java 中 Iterator 和 ListIterator 有什么区别?
- 问题 38. 为什么使用 Iterator 删除元素更加安全?
1.6、Java-8中的流处理
2、Java集合面试题解答
2.1、JavaSet集合相关–特性&方法
解答:
Set
是 Java 集合框架中的一个接口,它继承自 Collection
接口。Set
集合中的元素是无序的,并且不包含重复的元素。
Set
集合的主要特性包括:
Java 中的 HashSet
、LinkedHashSet
和 TreeSet
都是 Set
接口的实现类,它们具有上述的 Set
特性,但是在内部实现和性能上有所不同。例如,HashSet
是基于哈希表实现的,插入和查询的性能较高;LinkedHashSet
是在 HashSet
的基础上,增加了链表来保证元素的插入顺序;TreeSet
是基于红黑树实现的,元素会按照自然顺序或者自定义的顺序进行排序。
解答:
Set
接口在 Collection
接口的基础上,没有新增任何方法,主要的方法都继承自 Collection
接口。以下是 Set
接口中一些常见的方法:
以上就是 Set
接口中一些常见的方法,它们提供了丰富的功能,使得我们可以方便地对集合进行操作。
2.2、JavaSet集合相关-具体实现
解答:
HashSet
是基于 HashMap
实现的,底层采用 HashMap
来保存所有元素。因此,HashSet
的数据结构就是 HashMap
的数据结构。
HashMap
是一个散列表,它存储的内容是键值对 (key–value)。HashMap
通过键的哈希值进行快速查找,具有较高的查找和插入速度。
HashSet
中的元素实际上作为 HashMap
的键存在,而 HashMap
的值则存储了一个固定的对象 PRESENT
。因此,HashSet
中的元素不能重复,这是因为 HashMap
的键不能重复。
HashSet
的操作都是基于 HashMap
的操作来实现的,例如添加元素、删除元素、查找元素等。
解答:
LinkedHashSet
是 HashSet
的一个子类,它的底层是基于 LinkedHashMap
来实现的。
LinkedHashMap
是 HashMap
的一个子类,它在 HashMap
的基础上,增加了一个双向链表。这个双向链表连接了所有的键值对,定义了键值对的迭代顺序。迭代的顺序可以是插入顺序,也可以是访问顺序。
LinkedHashSet
中的元素实际上作为 LinkedHashMap
的键存在,而 LinkedHashMap
的值则存储了一个固定的对象 PRESENT
。因此,LinkedHashSet
中的元素不能重复,这是因为 LinkedHashMap
的键不能重复。
LinkedHashSet
的操作都是基于 LinkedHashMap
的操作来实现的,例如添加元素、删除元素、查找元素等。由于 LinkedHashSet
维护了一个运行于所有条目的双向链表,因此,可以在用迭代器遍历 LinkedHashSet
时,得到一个确定的顺序(插入的顺序)。
解答:
TreeSet
是基于 TreeMap
实现的,底层采用 TreeMap
来保存所有元素。因此,TreeSet
的数据结构就是 TreeMap
的数据结构。
TreeMap
是一个红黑树(自平衡的排序二叉树)。它存储的内容是键值对 (key–value)。TreeMap
通过键的自然顺序或者自定义的比较器进行排序,具有较高的查找和插入速度。
TreeSet
中的元素实际上作为 TreeMap
的键存在,而 TreeMap
的值则存储了一个固定的对象 PRESENT
。因此,TreeSet
中的元素不能重复,这是因为 TreeMap
的键不能重复。
TreeSet
的操作都是基于 TreeMap
的操作来实现的,例如添加元素、删除元素、查找元素等。由于 TreeSet
是基于 TreeMap
实现的,所以 TreeSet
的元素是有序的,元素的排序方式取决于构造 TreeSet
时提供的 Comparator
,或者依赖元素的自然顺序(Comparable
)。
TreeSet
是 SortedSet
接口的一个实现类,它提供了一个基于树结构的 Set
,元素可以按照自然顺序或者自定义的比较器进行排序。
解答:
EnumSet
是 Java 中的一个专门为枚举类型设计的集合类。它继承自 AbstractSet
,并实现了 Set
接口。
EnumSet.allOf(Class<E> elementType)
:创建一个包含指定枚举类型的所有元素的EnumSet
。EnumSet.noneOf(Class<E> elementType)
:创建一个指定枚举类型的空EnumSet
。EnumSet.of(E first, E... rest)
:创建一个最初包含指定元素的EnumSet
。EnumSet.range(E from, E to)
:创建一个包含从from
元素到to
元素范围内的所有元素的EnumSet
。EnumSet.copyOf(Collection<E> c)
或EnumSet.copyOf(EnumSet<E> s)
:创建一个与指定EnumSet
具有相同元素类型的EnumSet
,最初包含相同的元素(如果有的话)。
解答:
SortedSet
是 Java 集合框架中的一个接口,它继承自 Set
接口。SortedSet
接口为集合中的元素提供了一个总的排序。
以下是 SortedSet
的一些主要方法:
-
Comparator<? super E> comparator()
:返回用于对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回null
。 -
SortedSet<E> headSet(E toElement)
:返回此 set 的部分视图,其元素严格小于toElement
。 -
SortedSet<E> subSet(E fromElement, E toElement)
:返回此 set 的部分视图,其元素的范围从fromElement
(包括)到toElement
(不包括)。 -
SortedSet<E> tailSet(E fromElement)
:返回此 set 的部分视图,其元素大于等于fromElement
。
解答:
NavigableSet
是 Java 集合框架中的一个接口,它继承自 SortedSet
接口。NavigableSet
描述了一种可以通过搜索方法导航的数据结构。
以下是 NavigableSet
的一些特性:
以下是 NavigableSet
的一些主要方法:
TreeSet
是 NavigableSet
接口的一个实现类,它提供了一个基于树结构的 Set
,元素可以按照自然顺序或者自定义的比较器进行排序。
2.3、Java排序接口相关
解答:
Comparable
是 Java 中的一个接口,用于定义对象之间的自然排序规则。如果一个类实现了 Comparable
接口,那么它的对象就可以进行比较和排序。
Comparable
接口定义了一个 compareTo
方法,需要实现类进行重写。compareTo
方法接收一个同类型的对象作为参数,返回一个整数,有三种可能:
例如,下面的代码定义了一个 Person
类,实现了 Comparable
接口,按照年龄进行排序:
public class Person implements Comparable<Person> {
private String name;
private int age;
// ... 省略构造方法和 getter、setter 方法 ...
@Override
public int compareTo(Person other) {
return this.age - other.age;
}
}
List<Person> people = Arrays.asList(
new Person("Alice", 20),
new Person("Bob", 18),
new Person("Charlie", 22)
);
Collections.sort(people);
以上就是 Comparable
接口的基本概念和用法。通过实现 Comparable
接口,我们可以定义对象的自然排序规则,使得对象可以进行比较和排序。
- 问题 30. 请解释一下 Java 中的 Comparator 接口?
解答:
Comparator
是 Java 中的一个接口,用于定义对象之间的定制排序规则。如果一个类没有实现 Comparable
接口,或者实现了但是开发者希望有其他的排序方式,那么可以使用 Comparator
。
Comparator
接口定义了一个 compare
方法,需要开发者进行重写。compare
方法接收两个同类型的对象作为参数,返回一个整数,有三种可能:
例如,下面的代码定义了一个 Person
类,以及一个按照年龄排序的 Comparator
:
public class Person {
private String name;
private int age;
// ... 省略构造方法和 getter、setter 方法 ...
}
public class AgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
这样,我们就可以使用 AgeComparator
对 Person
对象进行排序:
List<Person> people = Arrays.asList(
new Person("Alice", 20),
new Person("Bob", 18),
new Person("Charlie", 22)
);
Collections.sort(people, new AgeComparator());
以上就是 Comparator
接口的基本概念和用法。通过实现 Comparator
接口,我们可以定义对象的定制排序规则,使得对象可以按照我们想要的方式进行排序。
2.4、Java集合并发相关
解答:
CopyOnWrite
是 Java 中的一种并发策略,主要应用于多线程环境下的读多写少的场景。Java 提供了 CopyOnWriteArrayList
和 CopyOnWriteArraySet
两个线程安全的集合类,它们的实现都采用了 “写时复制”(Copy-On-Write)的策略。
“写时复制” 的基本思想是:当我们需要修改集合(如添加、删除元素)时,不直接在当前集合上进行修改,而是先将当前集合进行复制,然后在新的副本上进行修改,最后再将引用指向新的副本。这样,读操作都是在原集合上进行,不需要加锁;写操作是在副本上进行,也不会影响读操作,实现了读写分离。
“写时复制” 的优点是可以实现高并发的读操作,适合读多写少的并发场景。但是,它也有一些缺点:
以上就是 “写时复制” 的基本原理和特点。在使用 CopyOnWriteArrayList
和 CopyOnWriteArraySet
时,需要根据实际的并发场景来权衡其优缺点。
解答:
CopyOnWriteArrayList
是 Java 中的一个线程安全的 List
实现,它是通过“写时复制”(Copy-On-Write)策略来保证并发安全的。
-
写时复制策略:当对
CopyOnWriteArrayList
进行修改操作(如add
、set
、remove
等)时,它并不直接在当前数组上进行修改,而是先将当前数组进行复制,然后在新的数组上进行修改,最后再将引用指向新的数组。这样可以保证在修改过程中不会影响到读操作,实现了读写分离。 -
读操作无锁:由于所有的写操作都是在新的数组上进行的,所以读操作是无锁的,可以直接读取,这对于读多写少的场景性能提升很大。
-
内存占用:由于每次写操作都需要复制一个新的数组,所以
CopyOnWriteArrayList
在内存占用上会比普通的ArrayList
大。
总的来说,CopyOnWriteArrayList
是一种适用于读多写少且需要线程安全的场景的 List
实现。但是由于写时复制策略,它在内存占用和写操作性能上有一定的开销。
解答:
“Fail Fast” 是 Java 集合框架中的一个错误检测机制。当多个线程对一个集合进行并发操作时,如果一个线程通过迭代器(Iterator)在遍历集合的过程中,其他线程修改了集合的结构(如添加、删除元素),那么正在遍历的线程会立即抛出 ConcurrentModificationException
异常。
“Fail Fast” 的主要目的是为了快速发现并发修改的问题,而不是等到程序运行一段时间后才发现问题。这种机制可以帮助我们尽早发现并发编程中的错误,避免出现难以预料的结果。
需要注意的是,“Fail Fast” 机制并不能保证在所有情况下都能检测到并发修改的问题,它只能尽最大可能地发现问题。另外,“Fail Fast” 机制并不是用来解决并发问题的,如果需要在多线程环境下安全地操作集合,应该使用线程安全的集合类,或者通过同步机制来保护非线程安全的集合。
解答:
“Fail Safe” 是 Java 集合框架中的一种错误处理机制。在 “Fail Safe” 机制下,当一个线程正在遍历集合的过程中,其他线程对集合进行修改,不会抛出 ConcurrentModificationException
异常。
“Fail Safe” 机制的实现通常是通过创建集合的副本来实现的。当进行遍历操作时,遍历的是原集合的副本,而不是原集合。因此,对原集合的修改不会影响到遍历操作,也就不会抛出 ConcurrentModificationException
异常。
Java 中的 CopyOnWriteArrayList
和 CopyOnWriteArraySet
就是使用了 “Fail Safe” 机制。这两个类在进行修改操作时,会创建原集合的副本,然后在副本上进行修改,最后再将引用指向新的副本。
需要注意的是,“Fail Safe” 机制虽然可以避免抛出 ConcurrentModificationException
异常,但是由于需要创建集合的副本,所以在内存占用和性能上会有一些开销。另外,由于遍历操作是在原集合的副本上进行的,所以可能无法看到其他线程对原集合的修改结果。
解答:
ConcurrentModificationException
是 Java 中的一个运行时异常,通常在多线程环境下,一个线程正在遍历集合的过程中,另一个线程修改了集合的结构(如添加、删除元素),那么正在遍历的线程可能会抛出这个异常。
这个异常通常是由 “Fail Fast” 机制引发的。“Fail Fast” 是 Java 集合框架中的一个错误检测机制,它的目的是为了尽早发现并发修改的问题,避免出现难以预料的结果。
需要注意的是,“Fail Fast” 机制并不能保证在所有情况下都能检测到并发修改的问题,它只能尽最大可能地发现问题。另外,“Fail Fast” 机制并不是用来解决并发问题的,如果需要在多线程环境下安全地操作集合,应该使用线程安全的集合类,或者通过同步机制来保护非线程安全的集合。
如果遇到 ConcurrentModificationException
异常,应该检查代码,确保在遍历集合的过程中,没有其他线程对集合进行修改。如果需要在遍历过程中修改集合,可以使用 Iterator
的 remove()
方法,或者使用 ListIterator
的 add()
和 set()
方法,这些方法可以安全地在遍历过程中修改集合。
2.5、Java迭代器相关
- 问题 36. Java 中迭代器 Iterator 是什么?
解答:
Iterator
是 Java 中的一个接口,它提供了一种统一的方式来遍历集合中的元素。Iterator
接口定义了三个方法:
在 Java 的集合框架中,所有的 Collection
子类都提供了一个 iterator()
方法,用于返回一个 Iterator
对象,通过这个对象可以遍历集合中的元素。
例如,下面的代码展示了如何使用 Iterator
遍历一个 ArrayList
:
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
这就是 Iterator
的基本概念和用法。通过 Iterator
,我们可以方便地遍历集合中的元素,而不需要关心集合的具体实现。
- 问题 37. Java 中 Iterator 和 ListIterator 有什么区别?
解答:
Iterator
和 ListIterator
都是 Java 中的迭代器接口,它们都提供了遍历集合元素的方法,但是 ListIterator
提供了更多的功能。
-
双向遍历:
Iterator
只能进行单向遍历,从前往后。而ListIterator
支持双向遍历,既可以从前往后,也可以从后往前。ListIterator
提供了hasPrevious
和previous
方法来实现从后往前的遍历。 -
添加元素:
ListIterator
提供了add
方法,可以在遍历过程中添加元素,而Iterator
不支持这个操作。 -
修改元素:
ListIterator
提供了set
方法,可以修改最后一次返回的元素,而Iterator
不支持这个操作。 -
获取元素索引:
ListIterator
提供了nextIndex
和previousIndex
方法,可以获取下一个或上一个元素的索引,而Iterator
不支持这个操作。 -
使用范围:
Iterator
可以应用于所有的Collection
子类,而ListIterator
只能应用于List
子类。
以上就是 Iterator
和 ListIterator
的主要区别。在需要进行更复杂的遍历操作时,可以选择使用 ListIterator
。
- 问题 38. 为什么使用 Iterator 删除元素更加安全?
解答:
使用 Iterator
删除集合中的元素更加安全,主要有以下两个原因:
-
避免并发修改异常:在使用
for-each
循环或者普通的for
循环遍历集合的过程中,如果直接调用集合的remove
方法删除元素,可能会抛出ConcurrentModificationException
异常。这是因为在遍历过程中,集合的结构发生了改变,但是这个改变并没有同步到正在进行的迭代过程中,所以会抛出异常。 -
避免索引问题:在使用普通的
for
循环遍历List
的过程中,如果直接调用List
的remove
方法删除元素,可能会出现索引问题。因为删除元素后,后面的元素的索引会发生改变,可能会导致跳过某些元素或者重复处理某些元素。而使用Iterator
的remove
方法删除元素,迭代器会正确地移动到下一个元素,不会出现这个问题。
因此,推荐在遍历集合的过程中,使用 Iterator
的 remove
方法删除元素。
2.6、Java-8中的流处理
解答:
Java 8 引入了一个新的 Stream
API,它提供了一种新的方式来处理集合。Stream
API 可以让我们以声明式的方式处理数据,使代码更简洁,易读。
以下是一些使用 Stream
API 处理集合的例子:
-
过滤:使用
filter()
方法可以过滤出满足条件的元素。List<String> names = Arrays.asList("John", "Jane", "Adam", "Tom"); List<String> result = names.stream() .filter(name -> name.startsWith("J")) .collect(Collectors.toList());
-
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> squares = numbers.stream() .map(n -> n * n) .collect(Collectors.toList());
-
排序:使用
sorted()
方法可以对元素进行排序。List<String> names = Arrays.asList("John", "Jane", "Adam", "Tom"); List<String> sortedNames = names.stream() .sorted() .collect(Collectors.toList());
-
统计:使用
count()
、max()
、min()
、average()
等方法可以进行统计。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); long count = numbers.stream().count(); Optional<Integer> max = numbers.stream().max(Integer::compare);
以上就是一些使用 Stream
API 处理集合的例子,Stream
API 还提供了很多其他的方法,如 reduce()
、collect()
、flatMap()
等,可以满足各种复杂的数据处理需求。
解答:
Java 8 在 Iterable
接口中添加了一个新的 forEach
方法,可以更简洁地遍历集合。forEach
方法接受一个 Consumer
函数式接口的实例作为参数,用于处理集合中的每个元素。
List<String> names = Arrays.asList("John", "Jane", "Adam", "Tom");
// 使用 lambda 表达式
names.forEach(name -> System.out.println(name));
// 使用方法引用
names.forEach(System.out::println);
在上述代码中,我们使用了 lambda 表达式和方法引用两种方式来处理集合中的每个元素。这两种方式都可以使代码更简洁,易读。
需要注意的是,forEach
方法的遍历顺序并不是固定的,它取决于具体的集合实现。如果需要固定的遍历顺序,应该使用 List
或者 LinkedHashSet
等有序的集合。
原文地址:https://blog.csdn.net/weixin_45187434/article/details/134093658
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_24752.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!