当前位置: 欣欣网 > 码农

面试官:你这样用集合,确定不会有问题?

2024-05-05码农

引言:不知道 fail-fast 机制,使用集合会出现各种各样的问题,其实大家在刷力扣的时候就可能会出现这样的场景,需要一边遍历集合,一边去添加或者修改集合中的元素,点击运行代码,可能报 ConcurrentModificationException 异常,这就是因为错误使用了集合,由于 fail-fast 错误检测机制,会抛出异常,这个问题也是面试中经常会问到的问题,本文将会从以下几个方面去讲,什么是 fail-fast 机制?什么时候会出现这种情况?为什么会出现这种情况?(源码分析)怎么解决集合的错误使用问题?

题目

面试官:fail-fast 机制了解吗?你这样使用集合不会有问题吗?。。

推荐解析

什么是 fail-fast 机制?

百度百科中只说明 fail-fast 机制是 Java 语言所特有的,其实是错误的,有小伙伴感兴趣可以修改下概念。fail-fast 是通用的系统设计思想,在 Python 2 中就有 fail-fast 机制,但可以调用 System 方法关闭安全校验,Python 3 直接不可关闭,推荐用异常进行捕获,C# 中也支持 fail-fast 机制,会抛出 InvalidOperationException 异常。

什么时候会出现 fail-fast 机制?

建议去测试的时候,单独测 1 个数据、2 个数据、3个数据,其实会有特例,数据量不同的时候,有些情况可能不会产生 fail-fast 机制,建议可以自己去检测一下。

代码举例如下

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
for (String list : lists) {
if (list.equals("Xiao Bai Tiao2")) {
lists.remove(list);
}
}
System.out.println(lists);

由于使用了增加 for 循环,但又不使用迭代器的 remove 去配合 hasNext(),此时的 remove 方法调用源码如下。

源码注释翻译

从此列表中删除第一个出现的指定元素(如果该元素存在)。如果列表中不包含该元素,则该元素不变。更正式地说,删除具有最低索引i的元素,使(o==null ?Get (i)==null: o.equals(Get (i)))(如果存在这样的元素)。如果此列表包含指定的元素(或者等价地,如果此列表因调用而更改),则返回true。如果列表中包含指定的元素,则返回:true。

关键方法是什么?

fastRemove 非常明显,集合方法 只会修改 modCount 数量。

privatevoidfastRemove(int index){
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null// clear to let GC do its work
}

点击 ConcurrentModificationException 的发生行,其实是 Iterator 迭代器的 next(),随便找个实现 Iterator 接口的实现类即可,ArrayList,Vector,都可以,源码搜这个方法。

finalvoidcheckForComodification(){
if (modCount != expectedModCount)
thrownew ConcurrentModificationException();
}

如果修改的数量不等于预期修改的数量(调用 Iterator 的 remove 才会修改 expectedModCount)。

简单来说,普通 remove 直接调用了 arraylist 底层的 fastRemove 只修改了 modCount,导致迭代器的 expecteModCount 和 modCount 不一致,因此迭代器的 fail-fast 机制被触发,会抛出异常,不理解的小伙伴直接搜我图中的源码,可以打断点进行 debug。实践出真知.jpg。

怎么解决 fail-fast 机制?

1)使用普通的 for 循环不会触发迭代器的 fail-fast 机制,but,list 元素会减少,下表会改变,很明显会出现漏删的情况。

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
for(int i = 0;i<lists.length;i++){
if (lists.get(i).equals("Xiao Bai Tiao1")) {
lists.remove(list);
}
}
System.out.println(lists);

2)使用迭代器的 Remove 方法

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
Iterator iterator = lists.iterator();
while(iterator.hasNext()){
if (iterator.next().equals("Xiao Bai Tiao1")) {
iterator.remove();
}
}
System.out.println(lists);

3)使用 fail-safe 集合安全的类

ConcurrentLinkedDeque<String> lists = new ConcurrentLinkedDeque<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
for (String list : lists) {
if (list.equals("Xiao Bai Tiao1")) {
lists.remove();
}
}
System.out.println(lists);

4)Java 8 的 Stream 流

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
lists = lists.stream().filter(list -> !list.equals("Xiao Bai Tiao1")).collect(Collectors.toList());
System.out.println(lists);

5)使用增强 for 循环,但是删除后立刻 break 退出。

List<String> lists = new ArrayList<>() {{
add("Xiao Bai Tiao1");
add("Xiao Bai Tiao2");
add("Xiao Bai Tiao3");
}};
for (String list : lists) {
if (list.equals("Xiao Bai Tiao2")) {
lists.remove(list);
break;
}
}
System.out.println(lists);

总结

fail-fast 就是多种语言在集合迭代遍历的同时去修改集合元素,因此会触发 fail-fast 机制,会抛出某些异常,但是依然可以有方法去进行解决,使用集合时要特别注意,阅读源码后即可了解底层调用原理,15 分钟基本就能完全理解了,现在囫囵吞枣,面试依然...建议大家还是沉下心来学习

其他补充

鱼聪明 AI 的回答:

鱼聪明 AI 地址:https://www.yucongming.com/

"Fail-fast"机制是一种软件设计原则,其主要思想是在软件出现问题时,尽早检测并立即停止程序的执行,以防止错误进一步扩大,从而提高系统的稳定性和可靠性。

出现fail-fast机制的情况通常包括以下几种:

  1. 并发修改:在多线程或并发环境下,如果多个线程同时对共享数据进行修改而没有合适的同步措施,就可能出现并发修改的情况。为了防止数据不一致或其他潜在的问题,系统可能会选择使用fail-fast机制来及时检测并报告这些并发修改。

  2. 数据校验失败:当输入的数据不符合预期的格式或约束条件时,系统可能会选择使用fail-fast机制来立即报告数据校验失败的情况,而不是继续执行可能会导致更严重错误的操作。

  3. 不合法状态:当系统处于不合法或无法处理的状态时,为了防止进一步的错误发生,系统可能会选择使用fail-fast机制来立即停止执行,避免更严重的后果。

解决fail-fast机制的方法通常包括以下几点:

  1. 异常处理:在出现错误时,使用异常机制来及时捕获和处理异常,以防止程序终止执行。通过合理的异常处理策略,可以使程序在出现问题时能够进行适当的处理而不至于崩溃。

  2. 错误检测与修复:在编写代码时,尽量避免可能导致fail-fast机制触发的情况,例如避免在迭代集合时进行并发修改,或者在操作之前进行合适的数据校验等。

  3. 合理的设计和测试:在软件设计阶段,考虑到可能出现的异常情况,并采取相应的措施来处理这些异常情况。同时,在开发过程中进行充分的测试,以确保程序在各种情况下都能够正确地运行并且不会触发fail-fast机制。

CSDN 某同学的回答

总结:之所以会抛出ConcurrentModificationException异常,是因为foreach底层是使用iterator来遍历,但是循环中元素的添加或者删除却是调用集合本身的方法,导致iterator在遍历过程中,发现有元素在自己不知不觉的情况下添加/删除了,就会抛出异常,告知用户可能会并发修改问题!

欢迎交流

在阅读完本文后,你应该了解了 fail-fast 机制的概念、出现场景、怎么解决 fail-fast 机制?在文末还有三个问题来检验你学习的情况,欢迎小伙伴在评论区积极留言!

1)fail-fast机制如何帮助提高软件系统的稳定性和可靠性?

2)在Java、Python和C#等编程语言中,有哪些常见的情况会触发fail-fast机制?

3)在使用fail-fast机制时,如何在代码中正确处理异常,以避免程序意外终止?

关注下方公众号卡片,持续收获重点面试题和最新的大厂面经!

往期推荐