Java基礎--爲什麼ArrayList,Vector等都不支持循環中remove?


JDK中有很多的數據結構,可以讓我們操作數據。
操作數據一般都是增刪改查,排序等操作。
其實,在Vector,ArrayList,LinkedList中,刪除有兩種方式進行刪除:
1.循環中刪除
2.直接刪除

1 Vector 直接刪除

在這裏插入圖片描述
在這裏插入圖片描述
直接刪除首先調用indexOf方法,得到目標元素的第一個序列,然後調用刪除指定序列元素的方法進行刪除。
在這裏插入圖片描述
在刪除指定序列元素的方法中,實際也是使用了System.arraycopy方法,將指定元素後面的所有元素,前移。
這樣就實現了指定序列元素刪除的目的。

2 Vector 遍歷元素

提到循環刪除,就不得不提到Vector的遍歷,畢竟循環刪除的基礎是循環。
那麼,Vector進行遍歷,是如何循環呢?
Vector有大概3種常見的遍歷方式:

2.1 for循環遍歷

for循環遍歷有三種寫法:普通for循環和增強for循環以及流的foreach循環。
普通for循環:

        for(int i = 0;i < vector.size();i++){
            People people = vector.get(i);
            people.setAge(people.getAge() + 1);
        }

增強for循環:

for (People people : vector) {
            people.setAge(people.getAge() + 1);
        }

在jdk8中,加入了lambda表達式,於是有了流的foreach循環

vector.stream().forEach(x -> x.setAge(x.getAge() + 1));

對於普通for循環,涉及到的方法非常簡單,我們給定序列值,要求數組返回指定序列的元素,因爲Vector是Object的數組,所以,返回的都是引用,我們基於引用進行修改,實際上也就修改了其存儲於對內存中對象的屬性值。
而對於基本類型的包裝類以及字符串,因爲其內部維護有常量表,通過get方法返回的可能是其具體的值,而不是引用,所以,使用普通for循環處理基本包裝類以及字符串的Vector可能存在修改失敗的問題。不過這個不在我們思考的範圍內.

對於增強for循環,其實現比普通for循環要複雜。Vector在其類內有一個私有內部類:
在這裏插入圖片描述
它實現了Iterator接口,實現了這個接口中的方法,就能實現增強for循環以及迭代器循環。
所以,嚴格意義來講,增強for循環是使用迭代器實現的。
流的for循環,是使用了jdk8的默認方法進行調度,然後調用的還是迭代器裏面的真正執行的方法:
在這裏插入圖片描述
首先獲取Collection中默認的stream方法,得到非並行的spliterator(分割器)
在這裏插入圖片描述
然後調用分割器的forearch方法:
在這裏插入圖片描述
在這裏插入圖片描述
真正調用的是分割器的forEachRemaining方法。
在Vector中,也有一個私有類,實現了分割器接口:
在這裏插入圖片描述
分割器的的方法中就有foreach調用的實現。
在這裏插入圖片描述
我們可以在裏面打斷點驗證。
在這裏插入圖片描述
從其調用堆棧,我們可以和清楚的驗證我們的分析是正確的。

2.2 迭代器循環

爲什麼我沒有在上面講增強for循環,是因爲,增強for循環使用迭代器實現的,所以,將增強for循環放在這裏更好一些。
首先,我們既然用過Vector,ArrayList或者LinkedList中的任意一種,那麼就肯定知道如何使用迭代器進行遍歷集合數組了:

Iterator<People> iterator = vector.iterator();
        while (iterator.hasNext()){
            People people = iterator.next();
            people.setAge(people.getAge() + 1);
        }

這基本上是固定模式的迭代器寫法。
所以,迭代器遍歷的順序就是:

  • 得到迭代器對象
  • 存在下一個對象
  • 獲取並偏移

而增強for循環,也是使用迭代器實現的,只不過,將上述模板代碼進行封裝語法糖,顯得更加簡單易懂、易用。
那麼,增強for循環是怎麼實現的呢?
我們知道,迭代器肯定需要調用hasNext方法確定是否進行下一次遍歷,以及使用next方法進行獲取遍歷元素以及偏移迭代器。
所以,我們在Vector的迭代器實現的hasNext方法與next方法打上斷點,查看其調用堆棧:
在這裏插入圖片描述
發現其toString方法也調用了hasNext
在這裏插入圖片描述
那麼,我們去掉toString方法的調用。
在這裏插入圖片描述
在這裏插入圖片描述
與我們猜想的一樣,在增強for循環中調用了hasNext方法確定是否可以進行下一次循環。
如果hasNext返回true,那麼調用next方法,獲取到元素
在這裏插入圖片描述
他這個應該是語法糖封裝,所以沒有顯示調用。

2.3 任意方向遍歷

我們遍歷Vector有兩種方向,從前往後,從後往前,前面所有的實現都是從前往後。
使用迭代器遍歷,只能從前往後。
使用普通for循環可以從後往前,而且可以實現任意連續數組遍歷。
比如10個元素,我需要遍歷後面的7個,而且是從後往前。
這樣使用普通for循環也能實現。
但是這樣存在一個問題,遍歷時,只能讀取,增加,卻不能刪除。
如果:
我需要遍歷後面7個元素,從後往前,刪除值等於8的元素,怎麼實現呢?
使用迭代器,增強for循環,普通for循環都無法實現。
使用JDK8的流操作倒是可以實現,但是其過程也是非常的繁瑣,性能比較慢。
所以,JDK提供了任意方向遍歷的方法:

        ListIterator<People> listIterator = vector.listIterator(7);
        while (listIterator.hasPrevious()){
            People people = listIterator.previous();
            people.setAge(people.getAge() + 2);
            if(people.getAge() == 7){
                listIterator.remove();
            }
        }

在這裏插入圖片描述
我們可以看到,它遍歷了前面7個元素,而且將操作後值等於7的元素進行刪除。
而且是從後往前進行遍歷的。
其從前往後遍歷:

ListIterator<People> listIterator1 = vector.listIterator();
        while (listIterator1.hasNext()){
            People people = listIterator1.next();
            people.setAge(people.getAge() + 3);
        }

在這裏插入圖片描述

2.4 Vector的foreach

當然,如果你僅僅想遍歷元素,那麼Vector也提供了foreachar方法
在這裏插入圖片描述

vector.forEach(x -> x.setAge(x.getAge() + 5));

在這裏插入圖片描述
所以,總體來說,想要遍歷元素,並進行修改,選擇還是很多的。
但是如果你要涉及到元素數量的改變,那麼,能使用迭代器或者說流操作,還是儘可能使用這些安全的操作,避免出現ConcurrentModificationException。

3. Vector迭代器刪除

我們前面講了,迭代器遍歷,使用到了next方法獲取元素以及偏移。
但是在next方法中會進行一個檢測:
在這裏插入圖片描述
在這裏插入圖片描述
這個方法會判斷modCount和expectedModCount是否一致,只有一致的條件下,集合數組纔會進行循環,否則因其fast-fail機制,會通過拋出異常,進行快速失敗。
expectedModCount是在創建迭代器對象時進行初始化的,值等於modCount
在這裏插入圖片描述
而我們的add,set,remove等方法,都會修改modCount的值。
請注意,Vector自己的方法時不會進行expectedModCount的修改,只有迭代器纔會維護這個expectedModCount的值。
那麼,在循環中,進行add,可以嗎?
在這裏插入圖片描述
答案也是不行的,在循環遍歷中,無法進行造成數組元素數量變化的操作,迭代器提供的刪除方法除外。
爲什麼迭代器提供的刪除方法可以實現刪除呢?
其核心原因是,迭代器沒有提供add方法,所以Vector進行add只能調用自己實現的add方法,而自己實現的add方法又不會去維護expectedModCount的值。
在循環中每次都會調用next方法進行獲取本次遍歷的元素,以及偏移到下一次遍歷的元素的位置,但是在next方法中會調用check方法,如果modCount與expectedModCount不相等,就會進行快速失敗。這就是爲什麼在循環時,不能進行增加的原因,刪除也只能調用迭代器實現的刪除方法。
因爲expectedModCount就是迭代器自己維護的變量。爲了保證迭代器自己的刪除操作成功,且能夠進行下一次循環,每次刪除都會強制將expectedModCount的值設置爲modCount的值:
在這裏插入圖片描述
而且迭代器調用的是Vector自己實現的remove方法:
在這裏插入圖片描述
這樣也維護了數組有效長度的可靠。

4. Vector不使用迭代器刪除元素

看了這些,那麼,我使用普通for循環能不能實現刪除與增加呢?
畢竟普通for循環沒有check方法:

        for(int i = 0;i < vector.size();i++){
            People people = vector.get(i);
            if (people.getAge() == 20){
                vector.add(new People(30));
            }
        }

在這裏插入圖片描述
答案是可以的。
那麼刪除呢?

        for(int i = 0;i < vector.size();i++){
            People people = vector.get(i);
            if (people.getAge() == 20 || people.getAge() == 30){
                vector.remove(people);
                i--;
            }
        }

在這裏插入圖片描述
答案是可以的,但是請注意,在刪除掉元素後,需要將我們的序列值縮小。

5. Vector流刪除元素

除了使用4中刪除,我們還可以使用JDK8中的流操作,刪除元素。

        vector = vector.stream().filter(people -> people.getAge() != 21).
                collect(Vector::new,Vector::add,(left,right)->left.addAll(right));

在這裏插入圖片描述
但是,通過這種流操作,涉及到重新構建,收集的問題,在不考慮多線程流操作的情況下,性能應該是比4中的方法的性能要差。

ArrayList與Vector大同小異。
而LinkedList其實現是雙鏈表實現,在元素數量的操作上,優於數組的。但是隨機訪問性能較差。

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