天天看點

ArrayList中modCount的作用

在ArrayList中有個成員變量modCount,繼承于AbstractList。

這個成員變量記錄着集合的修改次數,也就每次add或者remove它的值都會加1。這到底有什麼用呢?

先看下面一段測試代碼:

package temp;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class demo {
    public static void main(String[] args){
          List<String> list = new ArrayList<String>();
            //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            list.add("a");
            Iterator iterator = list.iterator();
            while(iterator.hasNext()){
                String str = (String) iterator.next();
                list.remove(str);
            }
    }
}
           

在使用疊代器周遊集合的時候同時修改集合元素。因為ArrayList被設計成非同步的,是以理所當然會抛異常。但是該抛什麼異常才能說明該問題呢?

首先得了解ArrayList的疊代器

public Iterator<E> iterator() {
        return new Itr();
    }
           

在調用list.iterator()的時候傳回的是一個Itr對象,它是ArrayList中的一個内部類。

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

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

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

主要關注3個點:

1、expectedModCount的初值為modCount

2、hasNext的判斷條件為cursor!=size,就是目前疊代的位置不是數組的最大容量值就傳回true

3、next和remove操作之前都會先調用checkForComodification來檢查expectedModCount和modCount是否相等

  如果沒checkForComodification去檢查expectedModCount與modCount相等,這個程式肯定會報ArrayIndexOutOfBoundsException

  這樣的異常顯然不是應該出現的(這些運作時錯誤都是使用者的邏輯錯誤導緻的,我們的JDK那麼高端,不會出現使用錯誤,我們隻抛出使用者造成的錯誤,而這個錯誤是設計者應該考慮的),為了避免出現這樣的異常,定義了檢查。是以抛出ConcurrentModificationException異常更能說明問題。

将測試代碼改成如下:

package temp;

import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class demo {
    public static void main(String[] args){
          List<String> list = new ArrayList<String>();
            //CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
            list.add("a");
            list.add("b");
            list.add("c");
            list.add("d");
            list.add("e");
            Iterator iterator = list.iterator();
            while(iterator.hasNext()){
                String str = (String) iterator.next();
                if(str.equals("d")){
                    list.remove(str);
                }else{
                    System.out.println(str);
                }
            }
    }
}
           

輸出卻是 a b c。

  因為在删除 d 的時候cursor為4,size也變成了4。是以hasNext就傳回為true了,循環結束,進而後面的元素也不會輸出了。

又想,為什麼不把hasNext()的判斷改為cursor <=size()呢?但是我們還有可能 add()這樣的話就會導緻資料混亂,事實上線程安全本身就不允許讀的時候被修改。

這種問題在多線程情況下,操作同一集合很容易暴露,就算改成同步的Vector問題還是會存在,需要使CopyOnWriteArrayList。

繼續閱讀