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
15public 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
15public 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
100 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
17L3 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
27public 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
44private 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
27public 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
21public 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内容请搜索靠谱客的其他文章。
发表评论 取消回复