java List 數組刪除元素


在 java 中,ArrayList 是一個很常用的類,在編程中經常要對 ArrayList 進行增、刪、改、查操作。之前在學校時一直認為刪除操作是最簡單的,現在才越發覺得自己愚蠢。只需要設置好預期條件的查詢才是最簡單的,刪除涉及到存儲空間的釋放,以及數組的遍歷等問題,在list的操作中相對還算小老哥呢。

這兩天在給小程序提供后台接口,因為設計的改變,需要對於已查詢出來的數組進行遍歷刪除。在使用 remove 方法對 ArrayList 進行刪除操作時,報 java.util.ConcurrentModificationException 異常,下面探討一下該異常的原因以及解決辦法。

import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        List<String> listA = new ArrayList<>();
        listA.add("a");
        listA.add("b");
        listA.add("c");
        listA.add("d");
        listA.add("e");
        listA.add("f");
        
        for(String str:listA){
            if (str == "c") {
                listA.remove(str);
            }
        }
    }
}

運行代碼發現,在調用 listA.remove(str) 時,會報 java.util.ConcurrentModificationException異常,如下圖:

Exception in thread "main" java.util.ConcurrentModificationException
2     at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
3     at java.util.ArrayList$Itr.next(ArrayList.java:851)
4     at com.zhang.Test.main(Test.java:19)

debug 單步發現,在刪除倒數第二個元素是,是不會報上面的錯誤的,除此之外都會報異常。既然發現了規律,那么可以從源碼去尋找原因。

我們可以發現 java for循環的實現,是將List 對象托管給迭代器 Iterator,即如果想對List 進行刪除操作,均需要經過 Iterator,否則在遍歷時會亂掉,故拋出 ConcurrentModificationException 異常。

那么,我們可以看一下 Iterator 迭代器迭代的步驟:

1、判斷是否有下個元素:iterator.hasNext() 

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

2、獲取下個元素並賦值給上面例子中的item變量:item = iterator.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時返回了異常,異常原因為 modCount != expectedModCount:

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

繼續跟源碼,發現:

  a.modCount 是 List 從被 new 新建之后被修改的次數,當 List 調用 remove()等方法時,modCount++;

  b.expectedModCount 是 Itreator 期望當前的List被修改的次數;

  c.在Iterator初始化的時候將modCount 的值賦給了expectedModCount;

那么,現在就很容易可以知道為什么會報異常了:

  1).modCount 會隨着調用List.remove方法而自動增減,而expectedModCount則不會變化,就導致modCount != expectedModCount;

  2).在刪除倒數第二個元素后,cursor=size-1,此時size=size-1,導致 hasNext 方法認為遍歷結束;

解決方案:

經過查閱源碼可以發現,iterator 也有一個 remove 方法,其中有一個重要的操作為 expectedModCount = modCount 這樣就保證了兩者的相等。

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

修改后的代碼如下:

import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        List<String> listA = new ArrayList<>();
        listA.add("a");
        listA.add("b");
        listA.add("c");
        listA.add("d");
        listA.add("e");
        listA.add("f");
        Iterator<String> it_b=listA.iterator();
        while(it_b.hasNext()){
            String a=it_b.next();
            if ("c".equals(a)) {
                it_b.remove();
            }
        }
} 
}

改完之后,啟動 run,運行順暢無比,借金星小姐姐(or 小哥哥??)的話作為結尾:完美~~

 

參考:

java.util.ConcurrentModificationException異常原因及解決方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM