专栏名称: 程序员鱼皮
鹅厂全栈开发,持续分享编程技法和实用项目
目录
相关文章推荐
媒哥媒体招聘  ·  新媒体|看理想招聘! ·  2 天前  
媒哥媒体招聘  ·  综艺 | 合心传媒嘿嘿TEAM摇人啦! ·  2 天前  
内蒙古自治区高级人民法院  ·  【媒体关注】人民法院报丨内蒙古:发布优化法治 ... ·  昨天  
任易  ·  DY副总裁,还不如AI懂舆论…… ·  2 天前  
51好读  ›  专栏  ›  程序员鱼皮

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

程序员鱼皮  · 公众号  ·  · 2024-05-05 12:10

正文

引言:不知道 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 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 数量。

private void fastRemove(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,都可以,源码搜这个方法。

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

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

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

怎么解决 fail-fast 机制?

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

List lists = new ArrayList<>() {{
    add("Xiao Bai Tiao1");
    add("Xiao Bai Tiao1");
    add("Xiao Bai Tiao2");
    add("Xiao Bai Tiao3");
}};

for(int i = 0;i     if (lists.get(i).equals("Xiao Bai Tiao1")) {
        lists.remove(list);
    }
}
System.out.println(lists);

2)使用迭代器的 Remove 方法

List 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 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 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 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 机制?在文末还有三个问题来检验你学习的情况,欢迎小伙伴在评论区积极留言!







请到「今天看啥」查看全文