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"));
     // 迭代刪除方式一
        for (String str : list) {
            System.out.println(str);
            if (str.contains("b")) {
                list.remove(str);
            }
        }
    // 迭代刪除方式二
        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--;
            }
        }
     // 迭代刪除方式三
        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();
            }
        }
     // 迭代刪除方式五
        for (Iterator<String> ite = list.iterator(); ite.hasNext();) {
            String str = ite.next();
            if (str.contains("b")) {
                list.remove(str);
            }
        }

    }
} 
方式一:報錯 java.util.ConcurrentModificationException
方式二:報錯:下標越界 java.lang.IndexOutOfBoundsException

    list移除了元素但size大小未響應變化,所以導致數組下標不對;
    list.remove(i)必須size--

    而且取出的數據的索引也不準確,同時需要做i--操作
 方式三:正常刪除,不推薦;每次循環都需要計算list的大小,效率低
 方式四:正常刪除,推薦使用
 方式五:報錯: java.util.ConcurrentModificationException

二:如果上面的結果算錯的話,先看下ArrayList的源碼(add和remove方法)

ArrayList繼承AbstractList,modCount是AbstractList中定義用於計算列表的修改次數的屬性。

public class ArrayList<E> extends AbstractList<E> // AbstractList定義了:protected transient int modCount = 0;

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

}

}

對此應該差不多可以理解了。ArrayList通過foreach迭代是調用的其內部類Itr的next方法。如果通過foreach循環,要去除某些元素,只能通過迭代器刪除。因爲迭代器刪除後會對expectedModCount = modCount設置,不會再循環過程因爲expectedModCount 和 modCount值不相等而拋出異常了。如果是通過ArrayList的刪除則只會對modCount進行更新,但是ArrayList內部迭代器Itr的屬性expectedModCount卻沒有得到更新,所以拋異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章