ArrayList迭代过程删除问题

一:首先看下几个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"));

        /**
         * 报错 java.util.ConcurrentModificationException
         */
        for (String str : list) {
            System.out.println(str);
            if (str.contains("b")) {
                list.remove(str);
            }
        }

        /**
         * 报错:下标越界 java.lang.IndexOutOfBoundsException
         * list移除了元素但size大小未响应变化,所以导致数组下标不对
         * list.remove(i)必须size--
         * 而且取出的数据的索引也不准确,同时需要做i--操作
         */
        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--;
            }
        }

        /**
         * 正常删除,不推荐
         * 每次循环都需要计算list的大小,效率低
         */
        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();
            }
        }

        /**
         * 报错: java.util.ConcurrentModificationException
         */
        for (Iterator<String> ite = list.iterator(); ite.hasNext();) {
            String str = ite.next();
            if (str.contains("b")) {
                list.remove(str);
            }
        }

    }
}

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

public class ArrayList<E> extends AbstractList<E>

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 = -1; // 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 < 0)

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();

}

}

对此应该差不多可以理解了。

时间: 2024-08-30 04:23:18

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

java ArrayList迭代过程中删除

第一种迭代删除方式: 第二种迭代删除方式: 第三种迭代删除: 第四种迭代删除: 第五种迭代删除: 第六种: ArrayList中remove()方法的机制,首先看源码: 真正的删除操作在fastRemove(),首先定义一个新列表的长度newSize,其值为原列表长度减一 (newS-ze = size-1),然后将 索引 i 之后的数组元素全部向前进一位(System.arraycopy(es, i + 1, es, i, newSize - i)),接着最后一个原数组的最后一个元素置为nul

ArrayList如何删除指定的几个数据?

import java.util.ArrayList; public class tttt { public static void main(String[] args) { ArrayList<Integer> arrayList = new ArrayList<Integer>(); ArrayList<Integer> arrayList2 = new ArrayList<Integer>(); for (int i = 0; i < 10;

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

引言:这是我在回答集合体系时,被问到的一个问题,也是因为没有深入学习所以回答的并不是很好,所以这两天看了一下,以下是我的一些回答与学习方法. 学习方法:我们学习,系统性的学习肯定是比零散的学习更有效的,针对一个问题,要对它涵盖的内容都进行学习,而不是只针对问题本事,死记硬背,所以我也是从头学起,从最顶级的接口到实现类的源码. 今天我主要讲是针对Arraylist以及LinkedList的区别进行回答,如果你想从头来一遍,这是我学习的文章.https://www.cnblogs.com/Carpe

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

因为你要是遍历了,arraylist 的长度就变了,容易数组越界和下标问题 public class Test {     public static void main(String[] args)  {         ArrayList<Integer> list = new ArrayList<Integer>();         list.add(2);         Iterator<Integer> iterator = list.iterator()

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

一.背景 在以前的随笔中说道过ArrayList的foreach迭代删除的问题:ArrayList迭代过程删除问题 按照以前的说法,在ArrayList中通过foreach迭代删除会抛异常:java.util.ConcurrentModificationException 但是下面这段代码实际情况却没报异常,是什么情况? 1 List<String> list = new ArrayList<String>(); 2 list.add("1"); 3 list.

Java之——删除ArrayList中的反复元素的2种方法

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/47414935 ArrayList是Java中最经常使用的集合类型之中的一个.它同意灵活加入多个null元素,反复的元素,并保持元素的插入顺序.在编码时我们经常会遇 到那种必须从已建成的ArrayList中删除反复元素的要求.这篇文章将给出两种从ArrayList中删除反复元素的方法. 方法1:使用HashSet删除ArrayList中反复的元素 在该方法中.我们使用HashSet

Java之——删除ArrayList中的重复元素的2种方法

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/47414935 ArrayList是Java中最常用的集合类型之一.它允许灵活添加多个null元素,重复的元素,并保持元素的插入顺序.在编码时我们经常会遇 到那种必须从已建成的ArrayList中删除重复元素的要求.这篇文章将给出两种从ArrayList中删除重复元素的方法. 方法1:使用HashSet删除ArrayList中重复的元素 在该方法中,我们使用HashSet来删除重复

ArrayList在foreach删除倒数第二个元素不抛并发修改异常的问题

平时我们使用ArrayList比较多,但是我们是否知道ArrayList在进行foreach的时候不能直接通过list的add或者move方法进行删除呢, 原因就是在我们进行foreach遍历的时候,其实底层原理就是使用了 iterator 迭代器进行操作的,我们在foreach中使用list的add 或者 move 方法:会导致并发修改异常抛出: ArrayList是java开发时非常常用的类,常碰到需要对ArrayList循环删除元素的情况.这时候大家都不会使用foreach循环的方式来遍历

ArrayList,LinkedList源码解析

在java中,集合这一数据结构应用广泛,应用最多的莫过于List接口下面的ArrayList和LinkedList; 我们先说List, 1 public interface List<E> extends Collection<E> { 2 //返回list集合中元素的数量,若数量大于Integer.MAX_VALUE,则返回Integer.MAX_VALUE 3 int size(); 4 5 //判读集合内是否没有元素,若没有元素返回true 6 boolean isEmpt