一:首先看下幾個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卻沒有得到更新,所以拋異常。