一:首先看下几个ArrayList循环过程删除元素的方法(一下内容均基于jdk7):

package list;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.prefs.Preferences; public class ListTest {
  public static void main(String[] args) {
  List<String> list = new ArrayList<>(Arrays.asList("a1", "ab2", "a3", "ab4", "a5", "ab6", "a7", "ab8", "a9"));
     // 迭代删除方式一
for (String str : list) {
System.out.println(str);
if (str.contains("b")) {
list.remove(str);
}
}
    // 迭代删除方式二
int size = list.size();
for (int i = 0; i < size; i++) {
String str = list.get(i);
System.out.println(str);
if (str.contains("b")) {
list.remove(i);
// size--;
// i--;
}
}
     // 迭代删除方式三
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
System.out.println(str);
if (str.contains("b")) {
list.remove(i);
}
}
     // 迭代删除方式四
for (Iterator<String> ite = list.iterator(); ite.hasNext();) {
String str = ite.next();
System.out.println(str);
if (str.contains("b")) {
ite.remove();
}
}
     // 迭代删除方式五
for (Iterator<String> ite = list.iterator(); ite.hasNext();) {
String str = ite.next();
if (str.contains("b")) {
list.remove(str);
}
} }
}
方式一:报错 java.util.ConcurrentModificationException
方式二:报错:下标越界 java.lang.IndexOutOfBoundsException     list移除了元素但size大小未响应变化,所以导致数组下标不对;
    list.remove(i)必须size--     而且取出的数据的索引也不准确,同时需要做i--操作
 方式三:正常删除,不推荐;每次循环都需要计算list的大小,效率低
方式四:
正常删除,推荐使用
方式五:报错: java.util.ConcurrentModificationException

二:如果上面的结果算错的话,先看下ArrayList的源码(add和remove方法)

ArrayList继承AbstractList,modCount是AbstractList中定义用于计算列表的修改次数的属性。

public class ArrayList<E> extends AbstractList<E> // AbstractList定义了:protected transient int modCount = 0;

 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
 {
 private static final long serialVersionUID = 8683452581122892189L;
 //设置arrayList默认容量 
 private static final int DEFAULT_CAPACITY = 10;
 //空数组,当调用无参数构造函数的时候默认给个空数组,用于判断ArrayList数据是否为空时
 private static final Object[]EMPTY_ELEMENTDATA = {};
 //这才是真正保存数据的数组
 private transient Object[] elementData;
 //arrayList的实际元素数量 
 private int size;
 //构造方法传入默认的capacity 设置默认数组大小
 public ArrayList(int initialCapacity) {
 super();
 if (initialCapacity < 0)
 throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
 this.elementData = new Object[initialCapacity];
 }
 //无参数构造方法默认为空数组 
 public ArrayList() {
super();
 this.elementData = EMPTY_ELEMENTDATA;
 }
 //构造方法传入一个Collection, 则将Collection里面的值copy到arrayList 
public ArrayList(Collection<? extends E> c) {
 elementData = c.toArray();
 size = elementData.length;
 // c.toArray might (incorrectly) not return Object[] (see 6260652) 
 if (elementData.getClass() != Object[].class)
 elementData = Arrays.copyOf(elementData, size, Object[].class);
 }
  //下面主要看看ArrayList 是如何将数组进行动态扩充实现add 和 remove 
 public boolean add(E e) {
 ensureCapacityInternal(size + 1); // Increments modCount!! 
 elementData[size++] = e;
 return true;
 }
 public void add(int index, E element) {
 rangeCheckForAdd(index);
 ensureCapacityInternal(size + 1); // Increments modCount!! 
System.arraycopy(elementData, index, elementData, index + 1,size - index);
 elementData[index] = element;
 size++;
 }
 private void ensureCapacityInternal(int minCapacity) {
// 通过比较elementData和EMPTY_ELEMENTDATA的地址来判断ArrayList中是否为空
// 这种判空方式相比elementData.length更方便,无需进行数组内部属性length的值,只需要比较地址即可。
 if (elementData == EMPTY_ELEMENTDATA) {
 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 }
 ensureExplicitCapacity(minCapacity);
 }
  private void ensureExplicitCapacity(int minCapacity) {
 modCount++;//ArrayList每次数据更新(add,remove)都会对modCount的值更新
 //超出了数组可容纳的长度,需要进行动态扩展 
if (minCapacity - elementData.length > 0)
 grow(minCapacity);
 }
 
//这才是ArrayList动态扩展的点
private void grow(int minCapacity) {
 int oldCapacity = elementData.length;
 //设置新数组的容量扩展为原来数组的1.5倍,oldCapacity >>1 向右位移,相当于oldCapacity/2, oldCapacity + (oldCapacity >> 1)=1.5*oldCapacity
int newCapacity = oldCapacity + (oldCapacity >> 1);
 //再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,
 //不够就将数组长度设置为需要的长度 
 if (newCapacity - minCapacity < 0)
 newCapacity = minCapacity;
 //判断有没超过最大限制
if (newCapacity - MAX_ARRAY_SIZE > 0)
 newCapacity = hugeCapacity(minCapacity);
 //将原来数组的值copy新数组中去, ArrayList的引用指向新数组
 //这儿会新创建数组,如果数据量很大,重复的创建的数组,那么还是会影响效率,
 //因此鼓励在合适的时候通过构造方法指定默认的capaticy大小
elementData = Arrays.copyOf(elementData, newCapacity);
 }
 
 private static int hugeCapacity(int minCapacity) {
 if (minCapacity < 0) // overflow 
 throw new OutOfMemoryError();
 return (minCapacity > MAX_ARRAY_SIZE) ?
 Integer.MAX_VALUE :
 MAX_ARRAY_SIZE;
 }
 // 删除方法
public boolean remove(Object o) {
// Object可以为null
if (o == null) {
// 如果传入的对象是null,则会循环数组查找是否有null的元素,存在则拿到索引index进行快速删除
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 对象非空则通过循环数组通过equals进行判断,最终还是要通过fastRemove根据索引删除
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 快速删除方法:基于下标进行准确删除元素
private void fastRemove(int index) {
// 删除元素会更新ArrayList的modCount值
modCount++;
// 数组是连续的存储数据结构,当删除其中一个元素,该元素后面的所有的元素需要向前移动一个位置
// numMoved 表示删除的下标到最后总共受影响的元素个数,即需要前移的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
// 在同一个数组中进行复制,把(删除元素下标后面的)数组元素复制(拼接)到(删除元素下标前的)数组中
// 但是此时会出现最后那个数组元素还是以前元素而不是null
System.arraycopy(elementData, index+1, elementData, index,numMoved);
// 经过elementData[--size] = null则把数组删除的那个下标移动到最后,加速回收
elementData[--size] = null; // clear to let GC do its work
}
 }
 
三:看下ArrayList进行foreach时所调用的迭代器(内部迭代器Itr)
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -; // index of last element returned; -1 if no such
// expectedModCount是Itr特有的,modCount是公共的
// expectedModCount和modCount默认是两者相等的;ArrayList进行删除修改都会更新modCount的值
// 当ArrayList通过foreach进入它的内部迭代器Itr时,expectedModCount就被赋值为modCount的值,后续ArrayList进行增加或删除,只会更新modCount,而不会同步更新expectedModCount
// 所以迭代器根据这两个值进行判断是否有并发性修改
int expectedModCount = modCount;
 
public boolean hasNext() {
return cursor != size;
}
// ArrayList通过foreach(即增强for循环)来循环是调用的是ArrayList中内部类Itr的next()
@SuppressWarnings("unchecked")
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];
}
// ArrayList中迭代器删除方法
public void remove() {
if (lastRet < )
throw new IllegalStateException();
checkForComodification();
 
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// 通过ArrayList中foreach(即通过ArrayList内部Itr的迭代器)进行删除元素
// 此时会进行赋值 expectedModCount = modCount;而不会抛出异常
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
对此应该差不多可以理解了。ArrayList通过foreach迭代是调用的其内部类Itr的next方法。如果通过foreach循环,要去除某些元素,只能通过迭代器删除。因为迭代器删除后会对expectedModCount = modCount设置,不会再循环过程因为expectedModCount 和 modCount值不相等而抛出异常了。如果是通过ArrayList的删除则只会对modCount进行更新,但是ArrayList内部迭代器Itr的属性expectedModCount却没有得到更新,所以抛异常。

ArrayList迭代过程删除问题的更多相关文章

  1. ArrayList的删除姿势你都知道了吗

    引言 前几天有个读者由于看了<ArrayList哪种遍历效率最好,你真的弄明白了吗?>问了个问题普通for循环ArrayList为什么不能删除连续重复的两个元素?其实这个描述是不正确的.正 ...

  2. java ArrayList迭代过程中删除

    第一种迭代删除方式: 第二种迭代删除方式: 第三种迭代删除: 第四种迭代删除: 第五种迭代删除: 第六种: ArrayList中remove()方法的机制,首先看源码: 真正的删除操作在fastRem ...

  3. ArrayList 实现删除重复元素(元素为对象类型)

    package 集合; import java.util.ArrayList;import java.util.Iterator; /* * 删除集合中的重复的元素(元素是对象形式的) *  * Li ...

  4. Java中ArrayList的删除元素总结

    Java中循环遍历元素,一般有for循环遍历,foreach循环遍历,iterator遍历. 先定义一个List对象 List<String> list = new ArrayList&l ...

  5. 为什么查询出来的数据保存到Arraylist?插入删除数据为啥用LinkedList?

    引言:这是我在回答集合体系时,被问到的一个问题,也是因为没有深入学习所以回答的并不是很好,所以这两天看了一下,以下是我的一些回答与学习方法. 学习方法:我们学习,系统性的学习肯定是比零散的学习更有效的 ...

  6. ArrayList实现删除重复元素(元素不是对象类型的情况)

    package 集合; import java.util.ArrayList;import java.util.Iterator; /* * 去除ArrayList里面的重复元素 *  * */pub ...

  7. arraylist 为什么 删除元素时要使用迭代器而不能使用遍历

    因为你要是遍历了,arraylist 的长度就变了,容易数组越界和下标问题 public class Test {     public static void main(String[] args) ...

  8. ArrayList中删除null元素效率比较

    package test; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; i ...

  9. ArrayList在foreach正常迭代删除不报错的原因

    一.背景 在以前的随笔中说道过ArrayList的foreach迭代删除的问题:ArrayList迭代过程删除问题 按照以前的说法,在ArrayList中通过foreach迭代删除会抛异常:java. ...

随机推荐

  1. JAVA基础总结2

    2.1 关键字 常用的关键字主要包括如下: 2.2 标识符 简单而言标识符它其实就是用于标识某些东西的符号. 2.3 注释的应用 注释说明 注释的作用: 注释的应用: 2.4 常量和变量 常量的定义与 ...

  2. PHP 注释 数据类型 变量的定义/输出 类型的获取/转换 可变变量

    注释方法: 1,单行注释:     // 2,  多行注释:     /*   */ 二,数据类型 1,integer(整数型):在三十二位操作系统中它的有效范围是:-2147483648~+2147 ...

  3. 关于Java和JavaScript对字符串截取处理的总结

    在JavaWeb开发中,经常需要对字符串进行处理,包括Java语言和JS语言,总是容易弄混淆,这里简单对比一下两种语言对于字符串截取方法. 一.先看Java public class StringDe ...

  4. C setjmp和longjmp

    #include <stdio.h> #include <setjmp.h> void test(jmp_buf *env) { printf("setjmp tes ...

  5. JavaScript的function参数的解释

    在js里面写function时其参数在内部表示为一个数组.也就是说:我们定义一个function,里面的参数和将来调用这个function时传入的实参是毫无关系的,如果我们要定义一个function ...

  6. EF6与Mysql疑难问题记录

    这几天公司架构调整,新的迭代后端使用了ABP框架与CodeFirst模式,执行过程中遇到了一个非必现很难定位的问题,特此记录. 现象 在程序访问MySql数据库时报了异常 System.Invalid ...

  7. Java常用类(四)之数组工具类Arrays

    前言 数组的工具类java.util.Arrays 由于数组对象本身并没有什么方法可以供我们调用,但API中提供了一个工具类Arrays供我们使用,从而可以对数据对象进行一些基本的操作. 一.Arra ...

  8. 使用grunt-init自动创建gruntfile.js和package.json文件

    使用grunt-init可以自动创建gruntfile.js和package.json文件.下面说一下过程: 1.全局安装grunt-init npm install -g grunt-init 2. ...

  9. spark join操作解读

    本文主要介绍spark join相关操作,Java描述. 讲述三个方法spark join,left-outer-join,right-outer-join 我们以实例来进行说明.我的实现步骤记录如下 ...

  10. Leetcode题解(一)

    1.Two Sum 题目 此题第一解题思路,就是最常见的方法,两个指针嵌套遍历数组,依次判断当前指针所指向的值是否满足条件.代码如下; class Solution { public: vector& ...