我是靠谱客的博主 健忘高跟鞋,这篇文章主要介绍ArrayList 循环Remove遇到的坑一、for循环遍历删除(list.remove)二、for循环迭代删除(iterator.remove)三、快速失败机制四、总结,现在分享给大家,希望可以做个参考。

ArrayList源码解析整体分析

ArrayList与LinkedList对比

ArrayList动态扩容机制

文章目录

  • 一、for循环遍历删除(list.remove)
    • ① tmp=3,非集合的最后一个元素
    • ② tmp=4,集合的最后一个元素
    • ③ 看字节码文件,分析导致这两种情况的原因
    • ④ 看迭代器源码分析原因
    • ⑤ 为什么对倒数第二个元素进行删除不会报异常,而对其他位置的删除会报异常
    • ⑥ 总结
  • 二、for循环迭代删除(iterator.remove)
    • ①迭代器demo测试
    • ②为何iterator.remove不会导致异常?
  • 三、快速失败机制
    • ①什么是快速失败机制?
    • ②机制是如何实现的?
  • 四、总结

本篇则主要聊聊List的循环删除操作,你在项目开发过程中有没有遇到相关的坑呢?

一、for循环遍历删除(list.remove)

① tmp=3,非集合的最后一个元素

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { list.add(i); } for (Integer tmp : list) { System.out.println(tmp); if (tmp == 3) { list.remove(tmp); } } System.out.println("........."); list.forEach(e -> System.out.println(e)); }

如上代码会报错吗?不报错会输出什么?
不会报错,但是输出结果是:

0
1
2
3

0
1
2
4

我以为会输出01234 … 0124但是显然和我的想法不一样。

② tmp=4,集合的最后一个元素

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { list.add(i); } for (Integer tmp : list) { System.out.println(tmp); if (tmp == 4) { list.remove(tmp); } } System.out.println("........."); list.forEach(e -> System.out.println(e)); }

如上代码会报错吗?不报错会输出什么?

复制代码
1
2
3
4
5
6
7
8
9
10
0 1 2 3 4 Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at collection.ArrayListDelTest.main(ArrayListDelTest.java:19)

ConcurrentModificationException异常了,为什么呢?

③ 看字节码文件,分析导致这两种情况的原因

输出结果无最后的value /删除最后一个报错,我们看字节码发现for循环,实际是走的List.iterator
字节码文件:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
L3 LINENUMBER 19 L3 FRAME CHOP 1 ALOAD 1 INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator; (itf) ASTORE 2 L6 FRAME APPEND [java/util/Iterator] ALOAD 2 INVOKEINTERFACE java/util/Iterator.hasNext ()Z (itf) IFEQ L7 ALOAD 2 INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object; (itf) CHECKCAST java/lang/Integer ASTORE 3

我们把程序转换一下,反正实际是走的迭代器,我们直接写成迭代器的形式。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { list.add(i); } // for (Integer tmp : list) { // System.out.println(tmp); // if (tmp == 4) { // list.remove(tmp); // } // } Iterator iterator = list.iterator(); while (iterator.hasNext()) { Integer tmp = (Integer) iterator.next(); System.out.println(tmp); if (tmp == 3) { list.remove(tmp); } } System.out.println("........."); list.forEach(e -> System.out.println(e)); }

④ 看迭代器源码分析原因

我们看一下类图关系:
在这里插入图片描述
有一个内部类 Itr 实现了 Iterator ,还有一个 ListItr 继承了 Itr (这个类初始化的时候会将 ArrayList 对象的 modCount 属性的值赋值给 expectedModCount)。

复制代码
1
2
3
4
5
6
7
8
9
10
以正确的顺序返回此列表中元素的迭代器。 此实现返回迭代器接口的直接实现,依赖于后备列表的size()get(int)remove(int)方法。 请注意,此方法返回的迭代器将抛出UnsupportedOperationException以响应其remove方法,除非列表的remove(int)方法被覆盖。 可以使此实现在面对并发修改时抛出运行时异常,如(受保护的) modCount字段的规范中所述。 回报: 以正确顺序遍历此列表中的元素的迭代器 public Iterator<E> iterator() { return new Itr(); }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private class Itr implements Iterator<E> { /** * Index of element to be returned by subsequent call to next. */ int cursor = 0; /** * Index of element returned by most recent call to next or * previous. Reset to -1 if this element is deleted by a call * to remove. */ int lastRet = -1; /** * The modCount value that the iterator believes that the backing * List should have. If this expectation is violated, the iterator * has detected concurrent modification. */ int expectedModCount = modCount; public boolean hasNext() { //判定cursor和size是否相等 return cursor != size(); } public E next() { //检测不通过则抛出异常 checkForComodification(); try { int i = cursor; E next = get(i); lastRet = i; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } //这个方法主要是检查光标是否越界的 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }

为什么会报错呢?我们看迭代循环,首先走hashNext,判定cursor和size是否相等?假如我们只有两个数,正常循环modCount 和expectedModCount一直是相等的,当删除某个元素,走了ArrayList的remove执行体,而当o不等于null时,实际执行的是fastRemove,里边有对modCount进行++操作。
在这里插入图片描述
在这里插入图片描述
删除某个元素执行结束后,modCount增加了1.正常循环到这里就应该结束了,因为我们只有两个值,但是得再次走一遍循环,去判定已经结束了循环。
在这里插入图片描述
hasNext不会报错,因为remove操作之后,size已经变成了1.所以两个值并不相等,继续执行next.
在这里插入图片描述
因为ArrayList的fastRemove操作对modCount进行了++,所以就导致了ConcurrentModificationException。

⑤ 为什么对倒数第二个元素进行删除不会报异常,而对其他位置的删除会报异常

你去测试吧,也就倒数第二个数据删除不会报错,其他的不管删除第几个,都会报错,这是为何?
主要原因也是在hasNext,
在这里插入图片描述
如上断点
我设置的i<3;也就是i=0,1,2
当删除元素1,也就是倒数第二个元素的value值,删除成功后,cursor变成了2,而此刻因为删除了一个元素,size也就变成了2.所以hasNext直接终止了循环。

⑥ 总结

迭代循环,正常并不会报错,就是因为remove是走的ArrayList的方法操作,所以导致两个数值不一致,进而报错。

二、for循环迭代删除(iterator.remove)

①迭代器demo测试

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) { List<Integer> list = new ArrayList<>(); for (int i = 0; i < 3; i++) { list.add(i); } // for (Integer tmp : list) { // System.out.println(tmp); // if (tmp == 4) { // list.remove(tmp); // } // } Iterator iterator = list.iterator(); while (iterator.hasNext()) { Integer tmp = (Integer) iterator.next(); System.out.println(tmp); if (tmp == 0) { iterator.remove(); } } System.out.println("........."); list.forEach(e -> System.out.println(e)); }

如上测试代码,只需要将list.remove变成iterator.remove,换成迭代器删除

②为何iterator.remove不会导致异常?

位于Itr类中的remove()方法,重写了Iterator接口中定义的default的remove()方法,用于移除遍历过的最后一个元素

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } /** *判断集合版本与迭代版本是否相同 **/ final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }

int lastRet = -1; // index of last element returned; -1 if no such
lastRet代表最后一次返回元素的下标,-1说明没有元素被返回

在这里插入图片描述
我们看源码发现,迭代器的remove其实也是调用的ArrayList的remove方法进行删除,但是不一样的是如上核心代码2,他对expectedModCount也进行了数据更新,所以当再次验证的时候,就不会报错。

三、快速失败机制

①什么是快速失败机制?

Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果你在迭代遍历某个容器的过程中,另一个进程介入其中,并且插入,删除或修改此容器的某个对象,就会立刻抛出ConcurrentModificationException。

比如用iterator迭代collection的时候,iterator就是另外起的一个线程,它去迭代collection,如果此时用collection.remove(obj)这个方法修改了collection里面的内容的时候,就会出现ConcurrentModificationException异常,这时候该迭代器就快速失败。

对于非并发集合,在用迭代器对其迭代时,若有其他线程修改了增减了集合中的内容,这个迭代会马上感知到,并且立即抛出 ConcurrentModificationException 异常,而不是在迭代完成后或者某些并发的场景才报错。

②机制是如何实现的?

是通过modCount域来实现的。modCount顾名思义是修改次数,对List内容的修改都会增加这个值,在迭代器初始化的过程中会将这个值赋值给迭代器的expectedModCount。

四、总结

在这里插入图片描述
阿里巴巴对在foreach理循环remove,强制通过iterator方式。

最后

以上就是健忘高跟鞋最近收集整理的关于ArrayList 循环Remove遇到的坑一、for循环遍历删除(list.remove)二、for循环迭代删除(iterator.remove)三、快速失败机制四、总结的全部内容,更多相关ArrayList内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(81)

评论列表共有 0 条评论

立即
投稿
返回
顶部