今天在网上看到一道面试题,感觉很有趣。
既然都这么问了,那肯定得说有问题啊。
那么ArrayList循环删除到底会不会出现什么问题?实践出真理》》》
先准备数据:
public static void main(String[] args){
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("sss");
arrayList.add("sss");
arrayList.add("xxx");
arrayList.add("aaa");
arrayList.add("bbb");
arrayList.add("xxx");
arrayList = deleteTemp(arrayList,"sss");
System.out.println(Arrays.toString(arrayList.toArray()));
}
正向循环删除
public static ArrayList<String> deleteTemp(ArrayList arrayList , String temp){
for (int i = 0 ; i < arrayList.size();i++){
if (arrayList.get(i).equals(temp)){
arrayList.remove(i);
}
}
return arrayList ;
}
结果:
[sss, xxx, aaa, bbb, xxx]
是不是很神奇,竟然有一个“有一个sss”没有被删除。
原因:
这是因为ArrayList是个动态数组。
当删除了第一个sss的时候,那时候的i是0。由于移除了一个元素,剩下的元素就会向前移一位,然后很奇妙的事情就发生了
第二个sss本来在第1位,但是第0位没有了,向前移动,此时第二个sss的下标就变成了0,然后然后这个本该被删除的sss就被错过了。
逆向循环删除
public static ArrayList<String> deleteTemp(ArrayList arrayList , String temp){
for (int i = arrayList.size()-1 ; i>=0; i--){
if (arrayList.get(i).equals(temp)){
arrayList.remove(i);
}
}
return arrayList ;
}
结果
[xxx, aaa, bbb, xxx]
删除成功!为什么倒过来删就成功了呢??
原因:
因为是从后面开始比遍历数组,后面遍历先碰到第一个符合的元素就会删除,但是并不会影响前面符合元素的位置。
附上一张for循环删除的ArraList数组元素的移动的示意图,见图就能知道上面的原因了
使用Iterator循环正序删除
public static ArrayList<String> deleteTemp(ArrayList arrayList , String temp){
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()){
if (iterator.next().equals(temp)){
arrayList.remove(iterator.next());
}
}
return arrayList ;
}
结果:
com.neu.deletearraylist.DeleteArrayList
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.neu.deletearraylist.DeleteArrayList.deleteTemp(DeleteArrayList.java:48)
at com.neu.deletearraylist.DeleteArrayList.main(DeleteArrayList.java:19)
结果报错,惊不惊喜,意不意外?
这就得跑去看源码了
查看ArrayList 的remove方法,看ArrayList 的remove方法有两个,都是重构了的方法,第一个remove方法是根据下标移除元素的,第二个remove方法是直接移除元素的,我们重点就看这个remove(object o)方法。
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
这里又调用了 fastRemove(index)方法
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
}
注意到:
有一个变量modCount++,这个modCount是记录数组修改的次数,还有一个变量是expectedModCount,这个expectedModCount是final修饰的,它的初始值等于modCount。
使用Iterator循环就会涉及到涉及到iterator迭代。ArrayList.java里面有一个next()的方法:
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
这个方法又调用了一个checkForComodification()方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这个方法很简单,当modCount != expectedModCount时就抛出异常
看了那么久现在终于有点眉目了吧
ArrayList的remov(object o)方法,这个方法又调用 fastRemove(index)方法,这个方法里面,modCount++,modCount的值变了,然后触发iterator迭代调用next()方法,这个next()方法就会调用checkForComodification()方法,这个方法一比较modCount 和expectedModCount就会发现不它们一样,就会报错了。
追加两点:
多线程反向遍历删除是没有问题的。
用Iterator中的remove方法也是没有问题的。
如何验证还请大家自行去尝试了或者去看其他大神的博客啦,因为小编的线程和迭代学得不是很到位。
有问题欢迎留言讨论哦。(_)