国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - java集合類遍歷的同時如何進行刪除操作

java集合類遍歷的同時如何進行刪除操作

2021-12-30 13:38NetWhite Java教程

這篇文章主要介紹了java集合類遍歷的同時如何進行刪除操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

java集合類遍歷的同時進行刪除操作

1. 背景

在使用java的集合類遍歷數據的時候,在某些情況下可能需要對某些數據進行刪除。往往操作不當,便會拋出一個ConcurrentModificationException,本方簡單說明一下錯誤的示例,以及一些正確的操作并簡單的分析下原因。

P.S. 示例代碼和分析是針對List的實例類ArrayList,其它集合類可以作個參考。

2. 代碼示例

示例代碼如下,可以根據注釋來說明哪種操作是正確的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class TestIterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        print(list);
 
        // 操作1:錯誤示范,不觸發ConcurrentModificationException
        System.out.println("NO.1");
        List<String> list1 = new ArrayList<>(list);
        for(String str:list1) {
            if ("4".equals(str)) {
                list1.remove(str);
            }
        }
        print(list1);
        // 操作2:錯誤示范,使用for each觸發ConcurrentModificationException
        System.out.println("NO.2");
        try{
            List<String> list2 = new ArrayList<>(list);
            for(String str:list2) {
                if ("2".equals(str)) {
                    list2.remove(str);
                }
            }
            print(list1);
        }catch (Exception e) {
            e.printStackTrace();
        }
        // 操作3:錯誤示范,使用iterator觸發ConcurrentModificationException
        try{
            System.out.println("NO.3");
            List<String> list3 = new ArrayList<>();
            Iterator<String> iterator3 = list3.iterator();
            while (iterator3.hasNext()) {
                String str = iterator3.next();
                if ("2".equals(str)) {
                    list3.remove(str);
                }
            }
            print(list3);
        }catch (Exception e){
            e.printStackTrace();
        }
        // 操作4: 正確操作
        System.out.println("NO.4");
        List<String> list4 = new ArrayList<>(list);
        for(int i = 0; i < list4.size(); i++) {
            if ("2".equals(list4.get(i))) {
                list4.remove(i);
                i--; // 應當有此操作
            }
        }
        print(list4);
        // 操作5: 正確操作
        System.out.println("NO.5");
        List<String> list5 = new ArrayList<>(list);
        Iterator<String> iterator = list5.iterator();
        while (iterator.hasNext()) {
            String str = iterator.next();
            if ("2".equals(str)) {
                iterator.remove();
            }
        }
        print(list5);
 
    }
 
    public static void print(List<String> list) {
        for (String str : list) {
            System.out.println(str);
        }
    }
}

P.S. 上面的示例代碼中,操作1、2、3都是不正確的操作,在遍歷的同時進行刪除,操作4、5能達到預期效果,推建使用第5種寫法。

3. 分析

首先,需要先聲明3個東東:

  • 1. for each底層采用的也是迭代器的方式(這個我并沒有驗證,是查找相關資料得知的),所以對for each的操作,我們只需要關注迭代器方式的實現即可。
  • 2. AraayList底層是采用數組進行存儲的,所以操作4實現是不同于其它(1、2、3、5)操作的,他們用的都是迭代器方式。
  • 3. 鑒于1、2點,其實本文重點關注的是采用迭代器的remove(操作5)為什么沒有問題,而采用集合的remove(操作1、2、3)就不行。

3.1 為什么操作4沒有問題

?
1
2
3
4
5
6
7
8
9
// 操作4: 正確操作
System.out.println("NO.4");
List<String> list4 = new ArrayList<>(list);
for(int i = 0; i < list4.size(); i++) {
    if ("2".equals(list4.get(i))) {
        list4.remove(i);
        i--; // 應當有此操作
    }
}

這個其實沒什么太多說的,ArrayList底層采用數組,它刪除某個位置的數據實際上就是把從這個位置下一位開始到最后位置的數據在數組里整體前移一位(基本知識,不多說明)。所以在遍歷的時候,重點其實是索引值的大小,底層實現是需要依賴這個索引 的,這也是為什么最后有個i--,因為我們刪除2的時候,索引值i為1,刪除的時候,就把索引為2到list.size()-1的數據都前移一位,如果不把i-1,那么下一輪循環時,i的值就為2,這樣就把原來索引值為2,而現在索引值為1的數據給漏掉了,這個地方需要注意一下。比如,如果原數據中索引1、2的數據都為2,想把2都刪除掉,如果不進行i--,那么把索引1處的2刪除掉后,下一次循環判斷時,就會把原來索引為2,現在索引為1的這個2給遺漏掉了。

3.2 采用迭代時ConcurrentModificationException產生的原因

其實這個異常是在迭代器的next()方法體調用checkForComodification()時拋出來的:

看下迭代器的這兩個方法的源碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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];
 }
 final void checkForComodification() {
     // 問題就在modCount與expectedModCount的值
     if (modCount != expectedModCount)
         throw new ConcurrentModificationException();
 }

1. 首先說明,modCount這個值是ArrayList的一個變量,而expectedModCount是迭代器的一個變量。

modCount:該值是在集合的結構發生改變時(如增加、刪除等)進行一個自增操作,其實在ArrayList中,只有刪除元素時這個值才發生改變。

expectedModCount:該值在調用集合的iterator()方法實例化迭代器的時候,會將modCount的值賦值給迭代器的變量 expectedModCount。也就是說,在該迭代器的迭代操作期間,expectedModCount的值在初始化之后便不會進行改變,而modCount的值卻可能改變(比如進行了刪除操作),這也是每次調用next()方法的時候,為什么要比較下這兩個值是否一致。

其實,我是把它們看作類似于CAS理論的實現來理解的,其實在迭代器遍歷的時候調用集合的remove方法,代碼上看起來是串行的,但是可以認為是兩個不同線程的并行操作這個ArrayList對象(我也是看了下其它資料,才試著這樣去理解)。

3.3 為什么在遍歷時使用迭代器的remove沒有問題

依據3.2條,我們知道,既然使用ArrayList的remove方法出現ConcurrentModificationException的原因在于modCount與expectedCount的值,那么問題就很明晰了,先看下迭代器的remove方法的源碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   public void remove() {
       if (lastRet < 0)
           throw new IllegalStateException();
       // 雖然這里也調用了這方法,但是本次我們可以先忽略,因為這個remove()方法是
       //iterator自已的,也就是可以看作遍歷和刪除是串行發生的,目前我們尚未開始進行移除
       //操作,所以這里的校驗不應該拋出異常,如果拋出了ConcurrentModificationException,
       //那只能是其它線程改了當前集合的結構導致的,并不是因為我們本次尚未開始的移除操作
       checkForComodification();
 
       try {
           // 這里開始進行移除
           ArrayList.this.remove(lastRet);
           cursor = lastRet;
           lastRet = -1;
           // 重新賦值,使用expectedModCount與modCount的值保持一致
           expectedModCount = modCount;
       } catch (IndexOutOfBoundsException ex) {
           throw new ConcurrentModificationException();
       }
}

注意看下我的注釋,在調用迭代器的remove方法時,雖然也是在調用集合的remove方法 ,但是因為這里保持了modCount與expectedModCount的數據一致性,所以在下次調用next()方法,調用checkForComodification方法時,也就不會拋出ConcurrentModificationException了。

3.4 為什么操作1沒有拋出ConcurrentModificationException

其實操作1雖然使用for each但是上面說過,其實底層依然是迭代器的方式,這既然是迭代器,然而采用集合的remove方法,卻沒有拋出ConcurrentModificationException, 這是因為移除的元素是倒數第二個元素的原因。

迭代器迭代的時候,調用hasNext()方法來判斷是否結束迭代,若沒有結束,才開始調用next()方法,獲取下一個元素,在調用next()方法的時候,因為調用 checkForComodification方法時拋出了ConcurrentModificationException。

所以,如果在調用hasNext()方法之后結束循環,不調用next()方法,就不會發生后面的一系列操作了。

既然還有最后一個元素,為什么會結束循環,問題就在于hasNext()方法,看下源碼:

?
1
2
3
public boolean hasNext() {
    return cursor != size;
}

其實每次調用next()方法迭代的時候,cursor都會加1,cursor就相當于一個游標,當它不等于集合大小size的時候,就會一直循環下去,但是因為操作1移除了一個元素,導致集合的size減一,導致在調用hasNext()方法,結束了循環,不會遍歷最后一個元素,也就不會有后面的問題了。

java集合中的一個移除數據陷阱

遍歷集合自身并同時刪除被遍歷數據

使用Set集合時:遍歷集合自身并同時刪除被遍歷到的數據發生異常

?
1
2
3
4
5
6
Iterator<String> it = atomSet.iterator();
  while (it.hasNext()) { 
   if (!vars.contains(it.next())) {
    atomSet.remove(it.next());
   }
  }

拋出異常:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at test2.Test1.main(Test1.java:16)

異常本質原因

Iterator 是工作在一個獨立的線程中,并且擁有一個 mutex 鎖。 Iterator 被創建之后會建立一個指向原來對象的單鏈索引表,當原來的對象數量發生變化時,這個索引表的內容不會同步改變,所以當索引指針往后移動的時候就找不到要迭代的對象,所以按照 fail-fast 原則 Iterator 會馬上拋出 java.util.ConcurrentModificationEx ception 異常。

所以 Iterator 在工作的時候是不允許被迭代的對象被改變的。但你可以使用 Iterator 本身的方法 remove() 來刪除對象, Iterator.remove() 方法會在刪除當前迭代對象的同時維護索引的一致性。

解決

使用Iterator的remove方法

?
1
2
3
4
5
6
Iterator<String> it = atomVars.iterator(); 
   while (it.hasNext()) { 
    if (!vars.contains(it.next())) {
     it.remove();
    }
   }

代碼能夠正常執行。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。

原文鏈接:https://blog.csdn.net/x763795151/article/details/84028314

延伸 · 閱讀

精彩推薦
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 欧美日韩一区在线观看 | 日韩成人在线网站 | 日韩精品一区二区在线视频 | 波多野结衣一区二区三区免费视频 | 免费观看av电影 | 国产午夜精品一区二区三区嫩草 | 久久亚洲二区 | 免费的成人毛片 | 国内精品一区二区 | 精品午夜久久 | 欧美日韩久久久 | 亚洲精品久久久久久国产精华液 | 欧美精品久久久 | 欧美久久综合 | 中文字幕亚洲欧美 | a黄视频| 国产精品久久久久久亚洲调教 | 日韩成人影院 | 国产精品久久久久aaaa九色 | 欧美在线网站 | 亚洲视频 欧美视频 | 欧美黄色一区 | 欧美日韩中文在线观看 | 九九热精品在线播放 | 久久久久久亚洲 | 黄色国产一级片 | 中文字幕一二三区 | 免费一级黄色毛片 | 亚洲电影在线观看 | 精品视频成人 | 一区二区三区免费在线观看 | 亚洲一区综合 | 亚洲成人毛片 | 亚洲狼人色 | 污污视频网址 | 国产精品a级 | 欧美一区二区三区视频 | www乱| 91中文在线观看 | 成人免费毛片aaaaaa片 | 国产一区二区在线免费观看 |