本文介绍: 大家好,我是栗筝i,从 2022 年 10 月份开始,我便开始致力于对 Java 技术栈进行全面而细致的梳理。这一过程,不仅是对我个人学习历程的回顾和总结,更是希望能够为各位提供一份参考。因此得到了很多读者的正面反馈。而在 2023 年 10 月份开始,我将推出 Java 面试题/知识点系列内容期望大家有所助益,让我们一起提升。今天与您分享的,是 Java 集合知识面试题系列总结篇(中篇),我诚挚地希望它能为您带来启发,并在您的职业生涯中起到助益作用。

大家好,我是栗筝i,从 2022 年 10 月份开始,我便开始致力于对 Java 技术栈进行全面而细致的梳理。这一过程,不仅是对我个人学习历程的回顾和总结,更是希望能够为各位提供一份参考。因此得到了很多读者的正面反馈

而在 2023 年 10 月份开始,我将推出 Java 面试题/知识点系列内容,期望大家有所助益,让我们一起提升。

今天与您分享的,是 Java 集合知识面试题系列的总结篇(中篇),我诚挚地希望它能为您带来启发,并在您的职业生涯中起到助益作用。衷心感谢每一位朋友的关注支持



1、Java集合面试题问题
1.1、JavaSet集合相关特性&方法
1.2、JavaSet集合相关-具体实现
1.3、Java排序接口相关
1.4、Java集合并发相关
1.5、Java迭代相关
1.6、Java-8中的流处理

2、Java集合面试题解答
2.1、JavaSet集合相关特性&方法

解答:

Set 是 Java 集合框架中的一个接口,它继承Collection 接口Set 集合中的元素无序的,并且不包含重复的元素。

Set 集合的主要特性包括:

  1. 无序Set 集合中的元素没有特定的顺序。也就是说,我们不能通过索引访问 Set 集合中的元素。

  2. 不可重复Set 集合不允许插入重复的元素。如果试图插入已经存在的元素,Set 集合不会报错,但是插入操作不会有任何效果

  3. 元素可为 nullSet 集合中可以添加 null 元素。

Java 中的 HashSetLinkedHashSetTreeSet 都是 Set 接口的实现类,它们具有上述的 Set 特性,但是在内部实现性能上有所不同。例如HashSet基于哈希实现的,插入查询性能较高;LinkedHashSet 是在 HashSet 的基础上,增加了链表来保证元素的插入顺序TreeSet 是基于红黑树实现的,元素会按照自然顺序或者自定义顺序进行排序

解答:

Set 接口在 Collection 接口的基础上,没有新增任何方法,主要的方法都继承自 Collection 接口。以下是 Set 接口中一些常见的方法:

  1. boolean add(E e):向集合中添加元素,如果集合已经包含该元素,则返回 false

  2. void clear()清空集合,移除所有元素。

  3. boolean contains(Object o)判断集合是否包含指定的元素。

  4. boolean isEmpty()判断集合是否为空

  5. Iterator<E&gt; iterator():返回一个用于遍历集合的迭代器。

  6. boolean remove(Object o):从集合中移除指定的元素。

  7. int size():返回集合中元素的数量。

  8. Object[] toArray():将集合转换数组

以上就是 Set 接口中一些常见的方法,它们提供了丰富的功能,使得我们可以方便地对集合进行操作

2.2、JavaSet集合相关-具体实现

解答:

HashSet 是基于 HashMap 实现的,底层采用 HashMap保存所有元素。因此,HashSet数据结构就是 HashMap数据结构

HashMap一个列表,它存储的内容是键值对 (keyvalue)。HashMap 通过键的哈希值进行快速查找,具有较高的查找插入速度。

HashSet 中的元素实际上作为 HashMap 的键存在,而 HashMap 的值则存储一个固定对象 PRESENT。因此,HashSet 中的元素不能重复,这是因为 HashMap 的键不能重复

HashSet 的操作都是基于 HashMap 的操作来实现的,例如添加元素、删除元素、查找元素等。

解答:

LinkedHashSetHashSet一个子类,它的底层是基于 LinkedHashMap 来实现的。

LinkedHashMapHashMap一个子类,它在 HashMap 的基础上,增加了一个双向链表。这个双向链表连接了所有的键值对,定义了键值对的迭代顺序迭代顺序可以是插入顺序,也可以是访问顺序。

LinkedHashSet 中的元素实际上作为 LinkedHashMap 的键存在,而 LinkedHashMap 的值则存储一个固定的对象 PRESENT。因此,LinkedHashSet 中的元素不能重复,这是因为 LinkedHashMap 的键不能重复

LinkedHashSet 的操作都是基于 LinkedHashMap 的操作来实现的,例如添加元素、删除元素、查找元素等。由于 LinkedHashSet 维护了一个运行于所有条目的双向链表,因此,可以在用迭代器遍历 LinkedHashSet 时,得到一个确定的顺序(插入的顺序)。

解答:

TreeSet 是基于 TreeMap 实现的,底层采用 TreeMap保存所有元素。因此,TreeSet数据结构就是 TreeMap数据结构

TreeMap 是一个红黑树(自平衡的排序二叉树)。它存储的内容是键值对 (keyvalue)。TreeMap 通过键的自然顺序或者自定义比较器进行排序具有较高的查找插入速度。

TreeSet 中的元素实际上作为 TreeMap 的键存在,而 TreeMap 的值则存储了一个固定的对象 PRESENT。因此,TreeSet 中的元素不能重复,这是因为 TreeMap 的键不能重复

TreeSet 的操作都是基于 TreeMap 的操作来实现的,例如添加元素、删除元素、查找元素等。由于 TreeSet 是基于 TreeMap 实现的,所以 TreeSet 的元素是有序的,元素的排序方式取决于构造 TreeSet 时提供的 Comparator,或者依赖元素的自然顺序(Comparable)。

TreeSetSortedSet 接口的一个实现类,它提供了一个基于树结构Set,元素可以按照自然顺序或者自定义比较器进行排序

解答:

EnumSet 是 Java 中的一个专门为枚举类型设计的集合类。它继承自 AbstractSet,并实现了 Set 接口。

以下是 EnumSet 的一些特性

  1. EnumSet 中的所有元素都必须来自同一个枚举类型,它在创建显式或隐式地指定。

  2. EnumSet有序的,其元素的顺序就是它们在源代码中的顺序。

  3. EnumSet 集合类的实现是非常高效和快速的,其大部分操作都是通过运算实现的。

  4. EnumSet 不允许使用 null 元素,如果尝试添加 null 元素,它会抛出 NullPointerException

  5. EnumSet线程安全的,如果多个线程同时修改 EnumSet需要进行同步处理

以下是创建 EnumSet 的一些方法:

  1. EnumSet.allOf(Class<E&gt; elementType)创建一个包含指定枚举类型的所有元素的 EnumSet
  2. EnumSet.noneOf(Class<E&gt; elementType)创建一个指定枚举类型的空 EnumSet
  3. EnumSet.of(E first, E... rest)创建一个最初包含指定元素的 EnumSet
  4. EnumSet.range(E from, E to)创建一个包含从 from 元素到 to 元素范围内的所有元素的 EnumSet
  5. EnumSet.copyOf(Collection<E> c)EnumSet.copyOf(EnumSet<E> s)创建一个与指定 EnumSet 具有相同元素类型的 EnumSet,最初包含相同的元素(如果有的话)。

解答:

SortedSet 是 Java 集合框架中的一个接口,它继承自 Set 接口。SortedSet 接口为集合中的元素提供了一个总的排序

以下是 SortedSet 的一些特性

  1. SortedSet 中的元素按照自然顺序或者自定义的比较器(Comparator)进行排序

  2. SortedSet 不允许插入 null 元素。如果尝试插入 null 元素,它会抛出 NullPointerException

  3. SortedSet 是线程不安全的,如果多个线程同时修改 SortedSet需要进行同步处理

以下是 SortedSet 的一些主要方法:

  1. Comparator<? super E> comparator():返回用于对此 set 中的元素进行排序的比较器;如果此 set 使用其元素的自然顺序,则返回 null

  2. E first():返回此 set 中当前第一个(最低)元素。

  3. SortedSet<E> headSet(E toElement):返回此 set 的部分视图,其元素严格小于 toElement

  4. E last():返回此 set 中当前最后一个(最高)元素。

  5. SortedSet<E> subSet(E fromElement, E toElement):返回此 set 的部分视图,其元素的范围fromElement(包括)到 toElement(不包括)。

  6. SortedSet<E> tailSet(E fromElement):返回此 set 的部分视图,其元素大于等于 fromElement

  • 问题 28. 请解释一下 Java 中的 NavigableSet

解答:

NavigableSet 是 Java 集合框架中的一个接口,它继承自 SortedSet 接口。NavigableSet 描述了一种可以通过搜索方法导航数据结构

以下是 NavigableSet 的一些特性:

  1. NavigableSet 中的元素按照自然顺序或者自定义的比较器(Comparator)进行排序。

  2. NavigableSet 提供了多种导航方法,例如获取小于/大于某个元素的最大/最小元素等。

  3. NavigableSet 不允许插入 null 元素。如果尝试插入 null 元素,它会抛出 NullPointerException

  4. NavigableSet 是线程不安全的,如果多个线程同时修改 NavigableSet需要进行同步处理

以下是 NavigableSet 的一些主要方法:

  1. E lower(E e):返回此 set 中严格小于给定元素的最大元素;如果不存在这样的元素,则返回 null

  2. E floor(E e):返回此 set 中小于等于给定元素的最大元素;如果不存在这样的元素,则返回 null

  3. E ceiling(E e):返回此 set 中大于等于给定元素的最小元素;如果不存在这样的元素,则返回 null

  4. E higher(E e):返回此 set 中严格大于给定元素的最小元素;如果不存在这样的元素,则返回 null

  5. E pollFirst()获取移除此 set 中的第一个(最低)元素;如果此 set 为空,则返回 null

  6. E pollLast()获取移除此 set 中的最后一个(最高)元素;如果此 set 为空,则返回 null

TreeSetNavigableSet 接口的一个实现类,它提供了一个基于树结构的 Set,元素可以按照自然顺序或者自定义的比较器进行排序。

2.3、Java排序接口相关
  • 问题 29. 请解释一下 Java 中的 Comparable 接口?

解答:

Comparable 是 Java 中的一个接口,用于定义对象之间的自然排序规则。如果一个类实现了 Comparable 接口,那么它的对象就可以进行比较和排序。

Comparable 接口定义了一个 compareTo 方法,需要实现类进行重写compareTo 方法接收一个同类型的对象作为参数,返回一个整数,有三种可能:

  1. 返回 0,表示 this 等于参数对象;
  2. 返回正数,表示 this 大于参数对象;
  3. 返回负数,表示 this 小于参数对象。

例如,下面的代码定义了一个 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;
    }
}

这样,我们就可以对 Person 对象进行排序:

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 方法接收两个同类型的对象作为参数,返回一个整数,有三种可能:

  • 返回 0,表示第一个参数等于第二个参数;
  • 返回正数,表示第一个参数大于第二个参数;
  • 返回负数,表示第一个参数小于第二个参数。

例如,下面的代码定义了一个 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();
    }
}

这样,我们就可以使用 AgeComparatorPerson 对象进行排序:

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集合并发相关
  • 问题 31. 请解释一下 Java 中的 CopyOnWrite

解答:

CopyOnWrite 是 Java 中的一种并发策略,主要应用多线程环境下的读多写少的场景。Java 提供了 CopyOnWriteArrayListCopyOnWriteArraySet 两个线程安全的集合类,它们的实现都采用了 “写时复制”(Copy-On-Write)的策略

“写时复制” 的基本思想是:当我们需要修改集合(如添加、删除元素)时,不直接在当前集合上进行修改,而是先将当前集合进行复制,然后在新的副本上进行修改,最后再将引用指向新的副本。这样,读操作都是在原集合上进行,不需要加锁;写操作是在副本上进行,也不会影响读操作,实现了读写分离。

“写时复制” 的优点是可以实现高并发的读操作,适合读多写少的并发场景。但是,它也有一些缺点:

  1. 内存占用:每次写操作都会复制一份新的集合,如果数据量大,会占用较多的内存

  2. 数据一致性:读操作可能无法读取最新数据,因为写操作是在副本上进行的。

  3. 写操作性能:由于需要复制新的集合,所以写操作的性能会比较低。

以上就是 “写时复制” 的基本原理和特点。在使用 CopyOnWriteArrayListCopyOnWriteArraySet 时,需要根据实际的并发场景来权衡其优缺点

  • 问题 32. 请解释一下 Java 中的 CopyOnWriteArrayList

解答:

CopyOnWriteArrayList 是 Java 中的一个线程安全的 List 实现,它是通过“写时复制”(Copy-On-Write)策略来保证并发安全的。

  1. 写时复制策略:当对 CopyOnWriteArrayList 进行修改操作(如 addsetremove 等)时,它并不直接在当前数组上进行修改,而是先将当前数组进行复制,然后在新的数组上进行修改,最后再将引用指向新的数组。这样可以保证在修改过程中不会影响到读操作,实现了读写分离。

  2. 读操作无锁:由于所有的写操作都是在新的数组上进行的,所以读操作是无锁的,可以直接读取,这对于读多写少的场景性能提升很大。

  3. 写操作加锁:写操作(修改、添加、删除等)需要加锁,防止多线程同时写入时导致数据不一致。

  4. 内存占用:由于每次写操作都需要复制一个新的数组,所以 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 中的 CopyOnWriteArrayListCopyOnWriteArraySet 就是使用了 “Fail Safe机制。这两个类在进行修改操作时,会创建原集合的副本然后副本上进行修改,最后再将引用指向新的副本。

需要注意的是,“Fail Safe机制虽然可以避免抛出 ConcurrentModificationException 异常,但是由于需要创建集合的副本,所以在内存占用和性能上会有一些开销。另外,由于遍历操作是在原集合的副本上进行的,所以可能无法看到其他线程对原集合的修改结果。

  • 问题 35. 请解释一下 Java 中的 ConcurrentModificationException?

解答:

ConcurrentModificationException 是 Java 中的一个运行异常,通常在多线程环境下,一个线程正在遍历集合的过程中,另一个线程修改了集合的结构(如添加、删除元素),那么正在遍历的线程可能会抛出这个异常

这个异常通常是由 “Fail Fast” 机制引发的。“Fail Fast” 是 Java 集合框架中的一个错误检测机制,它的目的是为了尽早发现并发修改的问题,避免出现难以预料的结果。

需要注意的是,“Fail Fast” 机制并不能保证在所有情况下都能检测到并发修改的问题,它只能尽最大可能地发现问题。另外,“Fail Fast” 机制并不是用来解决并发问题的,如果需要在多线程环境下安全地操作集合,应该使用线程安全的集合类,或者通过同步机制来保护非线程安全的集合。

如果遇到 ConcurrentModificationException 异常,应该检查代码,确保在遍历集合的过程中,没有其他线程对集合进行修改。如果需要在遍历过程中修改集合,可以使用 Iteratorremove() 方法,或者使用 ListIteratoradd()set() 方法,这些方法可以安全地在遍历过程中修改集合。

2.5、Java迭代器相关
  • 问题 36. Java 中迭代器 Iterator 是什么

解答:

Iterator 是 Java 中的一个接口,它提供了一种统一的方式来遍历集合中的元素。Iterator 接口定义了三个方法:

  1. hasNext()检查是否还有下一个元素,如果有则返回 true,否则返回 false

  2. next():返回当前元素,并将迭代器向前移动到下一个元素。

  3. remove():删除迭代器最后一次返回的元素。这个方法是可选的,不是所有的迭代器都支持

在 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 有什么区别

解答:

IteratorListIterator 都是 Java 中的迭代器接口,它们都提供了遍历集合元素的方法,但是 ListIterator 提供了更多的功能

  1. 双向遍历:Iterator 只能进行单向遍历,从前往后。而 ListIterator 支持双向遍历,既可以从前往后,也可以从后往前。ListIterator 提供了 hasPreviousprevious 方法来实现从后往前的遍历。

  2. 添加元素:ListIterator 提供了 add 方法,可以在遍历过程中添加元素,而 Iterator支持这个操作。

  3. 修改元素:ListIterator 提供了 set 方法,可以修改最后一次返回的元素,而 Iterator支持这个操作。

  4. 获取元素索引:ListIterator 提供了 nextIndexpreviousIndex 方法,可以获取下一个或上一个元素的索引,而 Iterator支持这个操作。

  5. 使用范围Iterator 可以应用于所有的 Collection 子类,而 ListIterator 只能应用List 子类

以上就是 IteratorListIterator 的主要区别。在需要进行更复杂的遍历操作时,可以选择使用 ListIterator

  • 问题 38. 为什么使用 Iterator 删除元素更加安全?

解答:

使用 Iterator 删除集合中的元素更加安全,主要有以下两个原因

  1. 避免并发修改异常:在使用 for-each 循环或者普通的 for 循环遍历集合的过程中,如果直接调用集合的 remove 方法删除元素,可能会抛出 ConcurrentModificationException 异常。这是因为在遍历过程中,集合的结构发生了改变,但是这个改变并没有同步到正在进行的迭代过程中,所以会抛出异常。

  2. 避免索引问题:在使用普通的 for 循环遍历 List 的过程中,如果直接调用 Listremove 方法删除元素,可能会出现索引问题。因为删除元素后,后面的元素的索引会发生改变,可能会导致跳过某些元素或者重复处理某些元素。而使用 Iteratorremove 方法删除元素,迭代器会正确移动到下一个元素,不会出现这个问题。

因此,推荐在遍历集合的过程中,使用 Iteratorremove 方法删除元素。

2.6、Java-8中的流处理
  • 问题 39. 如何在 Java 中使用 Java 8 的 Stream API 处理集合?

解答:

Java 8 引入了一个新的 Stream API,它提供了一种新的方式来处理集合。Stream API 可以让我们以声明式的方式处理数据,使代码简洁,易读。

以下是一些使用 Stream API 处理集合的例子

  1. 过滤:使用 filter() 方法可以过滤出满足条件的元素。

    List<String> names = Arrays.asList("John", "Jane", "Adam", "Tom");
    List<String> result = names.stream()
                               .filter(name -> name.startsWith("J"))
                               .collect(Collectors.toList());
    

    上述代码过滤出所有以 “J” 开头的名字

  2. 映射:使用 map() 方法可以将元素转换成其他形式。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    List<Integer> squares = numbers.stream()
                                   .map(n -> n * n)
                                   .collect(Collectors.toList());
    

    上述代码会将每个数映射成它的平方

  3. 排序:使用 sorted() 方法可以对元素进行排序。

    List<String> names = Arrays.asList("John", "Jane", "Adam", "Tom");
    List<String> sortedNames = names.stream()
                                    .sorted()
                                    .collect(Collectors.toList());
    

    上述代码会将名字按照字母顺序进行排序。

  4. 统计:使用 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() 等,可以满足各种复杂数据处理需求

  • 问题 40. 如何在 Java 中使用 Java 8 的 forEach 方法遍历集合?

解答:

Java 8 在 Iterable 接口中添加了一个新的 forEach 方法,可以更简洁地遍历集合。forEach 方法接受一个 Consumer 函数式接口的实例作为参数,用于处理集合中的每个元素。

以下是使用 forEach 方法遍历集合的例子

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进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注