ArrayList迭代過程刪除問題

一:首先看下幾個ArrayList循環過程刪除元素的方法(一下內容均基於jdk7):java

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方法)web

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卻沒有獲得更新,因此拋異常。
相關文章
相關標籤/搜索