關於集合遍歷並刪除報錯詳解

1、list集合正確刪除

list集合刪除不要使用加強for,建議使用for(int i;;)這種方法,注意這種方法刪除集合元素會致使索引前移致使遍歷問題bash

例如:多線程

private static void delFor() {
        List<String> blist = new ArrayList<>();
        blist.add("a");
        blist.add("b");
        blist.add("c");
        blist.add("d");
        blist.add("e");
        blist.add("f");


        for (int i = 0; i < blist.size(); i++) {
            if(blist.get(i).equals("b")) {
                blist.remove(blist.get(i));
                //這裏輸出被刪除的元素有問題,主要是索引改變,刪除自己沒問題
                System.out.println("刪除的元素是: " +  blist.get(i));
            }

        }
        System.out.println(blist);
    }
複製代碼

2、foreach刪除

加強for遍歷等效於使用迭代器, 在遍歷中修改元素數目會報ConcurrentModificationExceptionide

private static void delNormal() {
        List<String> alist = new ArrayList<>();
        alist.add("1");
        alist.add("2");
        alist.add("3");

        for (String item : alist) {
            if (item.equals("2")) {  //刪除2不報錯
                alist.remove(item);
                System.out.println("被刪除的元素"+item);
            }

        }
        System.out.println("遍歷刪除後的集合" + " " + alist);
    }
    
    結果:被刪除的元素2
          遍歷刪除後的集合  [1, 3]
複製代碼

上面結論你們確定知道,可是看這段代碼,刪除成功了,並無報錯,結論不對嗎?ui

再看一段代碼:this

private static void del() {
        List<String> blist = new ArrayList<>();
        blist.add("a");
        blist.add("b");
        blist.add("c");
        blist.add("d");
        blist.add("e");
        blist.add("f");
        for (String item : blist) {
            if(item.equals("b")) {
                blist.remove(item);
                System.out.println("刪除的元素是: "+ item );
            }
        }

        System.out.println("刪除後的集合爲:" +blist);
    }
複製代碼

這段代碼運行直接報錯,是上面描述的異常spa

private static void delForIterator() {
        List<String> blist = new ArrayList<>();
        blist.add("a");
        blist.add("b");
        blist.add("c");
        blist.add("d");
        blist.add("e");
        blist.add("f");
        Iterator it = blist.iterator();
        while(it.hasNext()) {
            String item = (String) it.next();
            if(item.equals("b")) {
                it.remove();
                System.out.println("刪除的元素是: "+ item );
            }

        }

        System.out.println("刪除後的集合爲:" +blist);
    }
複製代碼

正確刪除,沒問題線程

咱們看下迭代刪除的源碼:code

private class Itr implements Iterator<E> {
        int cursor;       // /將要訪問的元素的索引
        int lastRet = -1; // 上一個訪問元素的索引
        int expectedModCount = modCount;//expectedModCount爲預期修改值,初始化等於modCount(AbstractList類中的一個成員變量)

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

        @SuppressWarnings("unchecked")
        public E next() {
            //每次調用next()須要check
            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];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                //從新給expectedModCount值相等
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

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

在獲取一個Iterator對象時,會初始化成員變量orm

  • cursor(0)
  • lastRet(-1)
  • expectedModCount(ModCount=初始集合長度).

對於第一段代碼(加強for底層仍是調用迭代器),不報錯是由於在刪除2之後,調用hasNext()方法,cursor值移動至2,size此時變成2,相等,跳出循環,因此沒有報錯,這僅僅是個巧合而已。對象

增長for刪除報錯的主要緣由是每次調用next()方法,都會檢查expectedModCount和 ModCount值是否相等,當咱們刪除元素後,ModCount會改變與expectedModCount值不一樣,引發報錯。

使用iterator刪除時,看上面源碼,會再次賦值它們相等,因此不會報錯。

3、多線程狀況下的集合刪除

使用迭代器的iterator.remove()在單線程下是不會報錯的,可是在多線程狀況下,一個線程修改了集合的modCount致使另一個線程迭代時modCount與該迭代器的expectedModCount不相等,這也會報異常。

public class RemoveListForThreads implements Runnable {
    static List<String> alist = new ArrayList<>();

    public static void main(String[] args) {
        RemoveListForThreads s = new RemoveListForThreads();
        alist.add("a");
        alist.add("b");
        alist.add("c");
        alist.add("d");

        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1,
                TimeUnit.SECONDS, workQueue);

        for (int i = 0; i < 5; i++) {
            executor.execute(s);
        }
        executor.shutdown();
    }


    @Override
    public synchronized void run() {
        Iterator<String> iterator = alist.iterator();
            while (iterator.hasNext()) {
                String s = iterator.next();
                if (s.equals("c") && Thread.currentThread().getName().equals("pool-1-thread-1")) {
                    iterator.remove();
                    System.out.println(Thread.currentThread().getName() + " " + s);
                }
                System.out.println(Thread.currentThread().getName() + " " + s);
            }
            System.out.println(alist);
        }
}

複製代碼

上面這段代碼建立了一個線程池,開啓了五個線程,在run方法中遍歷並刪除「b」元素,若是該方法不加同步鎖sychronized,也會拋出異常

相關文章
相關標籤/搜索