Java基础:ArrayList常见的删除问题和解决方案

本文主要分析在循环遍历的情况下使用ArrayList删除元素可能会导致的问题,并给出解决方案。如果有更好的解决方案请务必留言。万分感谢!

一、要删除的元素在集合中不止一个

List list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        for (int i = 0;i

错误:第二个“2”不会被删除掉。

分析如下:arraylist删除方法源码如下:

//remove方法,最终会调用fastRomove()
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;
    }
 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
    }

这段代码主要逻辑是:如果arraylist删除了一个元素,则将此元素后的所有元素往前移一位,并将集合长度-1。

这里简单介绍一下System.arrayCopy()方法:如果两个数组相同,则将原数组的(index+1)到(index+1)+numMoved位置的元素平移到index到index+numMoved位置。

因为元素移动,所以在遍历到第一个“2”(下标为1)并删除掉后,第二个紧随的“2”会移动到下标为1的位置。但下次遍历时,会从下标2继续遍历。所以会有漏网之鱼。

解决方案:可以采用倒序的遍历方式来避免遗漏的情况发生:

for (int i = list.size()-1; i >=0 ; i--) {
            list.remove("2");
}

二、不要使用加强for循环增加或删除元素

这段代码执行后会抛出一个异常。

for (String str:list) {
          if ("2".equals(str)){
               list.remove(str);
           }
}
异常如下:
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 com.lifeisftc.collection.list.ArrayListRemoveError.main(ArrayListRemoveError.java:32)

下边逐步分析:

1.众所周知加强for循环是使用 Iterator 中的方法实现的。首先寻找ArrayList中使用的 Iterator。并找到hasNext()和next()方法

/**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @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];
        }

通过debug调试,发现问题出现在next()方法中的第一行:checkForComodification();翻看此方法代码:

 final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
 }

发现在modCount和expectedModCount不相等时,会抛出此异常。这两个属性的含义是:

modCount:modCount到底是干什么的,所以对集合进行的任何增、删、改操作都会使该值+1.

expectedModCount:在Itr类初始化时将modCount值复制过来。也就是在加强for循环开始时,expectedModCount的值已经确定了。那么在之后的循环过程中,如果进行了增删改操作,modeCount++,但expectedModCount不变,在checkForComodification()方法中,就会抛出此异常导致遍历结束。

另外的:如果删除的是集合中倒数第二个元素时,不会抛出此异常,因为在调用next方法时,首先会调用hasNext()方法,此时,由于删除元素,集合的长度减少1,hasNext()方法false,遍历停止。

解决方法:不要在加强循环中使用集合的方法删除元素。可以使用Iterator中的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();
            }
        }

 

你可能感兴趣的:(java基础)