一、list集合正確刪除
list集合刪除不要使用增強for,建議使用for(int i;;)這種方法,注意這種方法刪除集合元素會導致索引前移導致遍歷問題
例如:
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);
}
複製程式碼
二、foreach刪除
增強for遍歷等效於使用迭代器, 在遍歷中修改元素數目會報ConcurrentModificationException
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]
複製程式碼
上面結論大家肯定知道,但是看這段程式碼,刪除成功了,並沒有報錯,結論不對嗎?
再看一段程式碼:
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);
}
複製程式碼
這段程式碼執行直接報錯,是上面描述的異常
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);
}
複製程式碼
正確刪除,沒問題
我們看下迭代刪除的原始碼:
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物件時,會初始化成員變數
- cursor(0)
- lastRet(-1)
- expectedModCount(ModCount=初始集合長度).
對於第一段程式碼(增強for底層還是呼叫迭代器),不報錯是因為在刪除2以後,呼叫hasNext()方法,cursor值移動至2,size此時變成2,相等,跳出迴圈,所以沒有報錯,這僅僅是個巧合而已。
增加for刪除報錯的主要原因是每次呼叫next()方法,都會檢查expectedModCount和 ModCount值是否相等,當我們刪除元素後,ModCount會改變與expectedModCount值不同,引起報錯。
使用iterator刪除時,看上面原始碼,會再次賦值它們相等,所以不會報錯。
三、多執行緒情況下的集合刪除
使用迭代器的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,也會丟擲異常