概述
ArrayList源码解析整体分析
ArrayList与LinkedList对比
ArrayList动态扩容机制
文章目录
- 一、for循环遍历删除(list.remove)
- ① tmp=3,非集合的最后一个元素
- ② tmp=4,集合的最后一个元素
- ③ 看字节码文件,分析导致这两种情况的原因
- ④ 看迭代器源码分析原因
- ⑤ 为什么对倒数第二个元素进行删除不会报异常,而对其他位置的删除会报异常
- ⑥ 总结
- 二、for循环迭代删除(iterator.remove)
- ①迭代器demo测试
- ②为何iterator.remove不会导致异常?
- 三、快速失败机制
- ①什么是快速失败机制?
- ②机制是如何实现的?
- 四、总结
本篇则主要聊聊List的循环删除操作,你在项目开发过程中有没有遇到相关的坑呢?
一、for循环遍历删除(list.remove)
① tmp=3,非集合的最后一个元素
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,集合的最后一个元素
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));
}
如上代码会报错吗?不报错会输出什么?
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
字节码文件:
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
我们把程序转换一下,反正实际是走的迭代器,我们直接写成迭代器的形式。
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)。
以正确的顺序返回此列表中元素的迭代器。
此实现返回迭代器接口的直接实现,依赖于后备列表的size() 、 get(int)和remove(int)方法。
请注意,此方法返回的迭代器将抛出UnsupportedOperationException以响应其remove方法,除非列表的remove(int)方法被覆盖。
可以使此实现在面对并发修改时抛出运行时异常,如(受保护的) modCount字段的规范中所述。
回报:
以正确顺序遍历此列表中的元素的迭代器
public Iterator<E> iterator() {
return new Itr();
}
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测试
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()方法,用于移除遍历过的最后一个元素
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 循环Remove遇到的坑一、for循环遍历删除(list.remove)二、for循环迭代删除(iterator.remove)三、快速失败机制四、总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复