Java 基礎(二)集合源碼解析 Iterator

首先,在探索集合以前,咱們先來思考一個問題,集合是什麼?java

針對一個特定的問題,若是事先不知道須要多少個對象,或者它們的持續時間有多長,那麼也不知道如何保存那些對象。既然如此,怎樣才能知道那些對象要求多說空間呢?事先上根本沒法提早知道,除非進入運行期。
在面向對象的設計中,大多數問題的解決辦法彷佛都有些輕率——只是簡單地建立另外一種類型的對象。用於解決特定問題的新型對象容納了指向其餘對象的引用。固然,也能夠用數組來作一樣的事情,那是大多數語言都具備的一種功能。 但不能只看到這一點。這種新對象一般叫做「集合」(亦叫做一個「容器」)。在 須要的時候,集合會自動擴充本身,以便適應咱們在其中置入的任何東西。因此 咱們事先沒必要知道要在一個集合裏容下多少東西。只需建立一個集合,之後的工做讓它本身負責好了。
設計模式

上文摘抄自《Thinking in Java》,集合解決的問題是,在編譯期間不知道要多少個對象,可是數組必須在申明的時候明確指明數組長度,若是食用數組,申請太多的空間就會形成資源浪費,若是申請太少空間,就不夠用。因此引出了一個概念叫「容器」,來解決這個問題,這個容器就是咱們今天要研究的對象--「集合」。數組

咱們先來看一下類關係圖~bash

Java 提供的集合都在 Java.utils 包下,集合主要分兩類,Collection 和 Map。咱們用到的各類類型的集合,都是實現自這兩個接口。集合的實現類有不少,開發過程當中,咱們須要根據不一樣的需求,選擇合適的集合設計,以便高效率的解決咱們的實際問題。至於什麼場景用哪種類型的容器,使用這種容器能帶來哪些好處,這就是咱們要研究的核心點,也是咱們用好 Java 集合的精髓。數據結構

磨刀不誤砍柴工,咱們在探索集合的架構設計以前,咱們先來研究一下Iterator。架構

Iterator

Iterator :[計]迭代器,迭代程序ui

迭代器,這裏用到的就是設計模式中的迭代器模式。this

迭代器模式
定義:提供一種方法訪問一個容器對象中各個元素,而又不暴露該對象的內部細節。spa

這裏咱們的重點不是迭代器模式,對「迭代器模式」感興趣的童鞋能夠自行去了解一波。線程

先來看看接口 Iterator 的設計。

public interface Iterator<E> {
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> var1) {
        Objects.requireNonNull(var1);

        while(this.hasNext()) {
            var1.accept(this.next());
        }
    }
}複製代碼

一共四個方法,其中hasNext()和 next()方法是迭代必須方法。remove()和forEachRemaining()方法有默認實現,小夥伴不要糾結接口怎麼會有默認實現方法,這是 Java 8 的新特性。

  • hasNext():是否有下一個元素
  • next():獲取下一個元素
  • remove():刪除當前元素,非必須的方法,有須要可重寫實現。
  • forEachRemaining():給剩下來全部元素作了一個自定義的相同操做。非必須的方法,有須要可重寫實現。

#fail-fast 與 ConcurrentModificationException

fail-fast:是java集合(Collection)中的一種錯誤機制。當多個線程對同一個集合的內容進行操做時,就可能會產生fail-fast事件。
ConcurrentModificationException:出現 fail-fast 問題的時候就會拋出這個異常。

可能問題描述得有點抽象,我舉個例子:假設有個 ArrayList 集合A,A裏面包含10個元素,分別是0~9。假設線程a在獲取第5個元素的過程當中,線程b操做A刪除了第一個元素。那麼問題來了,此時a線程是獲取的到結果是5,可是個人本意應該是取到結果4,此時程序發生了錯誤,所以產生 fail-fast 問題,遂拋出異常。

###解決方案

  1. 在遍歷過程當中全部涉及到改變modCount值得地方所有加上synchronized。
  2. 用 CopyOnWriteArrayList,ConcurrentHashMap 替換 ArrayList, HashMap,它們的功能和名字同樣,在寫入時會建立一個 copy,而後在這個 copy 版本上進行修改操做,這樣就不會影響原來的迭代。不過壞處就是浪費內存。

Iterator 實現迭代功能

Iterator 的實現類通常之內部類的形式寫在集合類裏面。功能的實現是根據各類集合實現的特定實現,好比說 ArrayList 和 LinkedArrayList 的數據結構不同,因此 Iterator 實現也不同。

這裏以 ArrayList 的 Iterator 舉例子講一下 Iterator 的代碼實現。
在看源碼以前,咱們先來回顧一下 Iterator 的使用。

不使用 Iterator 遍歷集合是這樣的:
for(int i=0; i<list.size();i++){  
        // ... 
    } 
//使用 Iterator 遍歷集合是這樣的:

Iterator iterator = list.iterator();
while (iterator.hasNext()){
    Object obj = iterator.next()
}複製代碼

通常狀況,若是隻是遍歷獲取集合的全部元素,我選擇使用第一種方式,由於用 iterator 感受好麻煩的樣子。可是確定不少童鞋都犯過一個這樣的錯誤,咱們在 for 循環裏面對集合進行了remove操做,可是最後的結果和咱們指望的不同,這時候老手告訴你,集合不能這樣操做,若是你要remove,請用 iterator操做,那樣不會出問題,因而,咱們默默的記下了這個結論。稍後,咱們會在Iterator 的源碼裏面找到緣由。

private class Itr implements Iterator<E> {
        int cursor;//當前角標位置
        int lastRet;//用來標記當前須要刪除的元素角標
        int expectedModCount;//ArrayList元素個數

    private Itr() {
        this.lastRet = -1;//角標默認指向-1
        this.expectedModCount = ArrayList.this.modCount;
    }

    public E next() {
        this.checkForComodification();//檢查 fail-fast
        int var1 = this.cursor;
        if(var1 >= ArrayList.this.size) {
            throw new NoSuchElementException();//角標越界
        } else {
            Object[] var2 = ArrayList.this.elementData;//ArrayList的底層實現實際上就是一個數組
            if(var1 >= var2.length) {//double check
                throw new ConcurrentModificationException();
            } else {
                this.cursor = var1 + 1;
                return var2[this.lastRet = var1];
            }
        }
    }

    public boolean hasNext() {
        //檢查是否大於數組長度
        return this.cursor != ArrayList.this.size;
    }

    public void remove() {
        if(this.lastRet < 0) {
            throw new IllegalStateException();
        } else {
            this.checkForComodification();
            try {
                ArrayList.this.remove(this.lastRet);//調用ArrayList的remove
                this.cursor = this.lastRet;//cursor
                this.lastRet = -1;//避免同時調用兩次remove
                this.expectedModCount = ArrayList.this.modCount;//更新數組長度
            } catch (IndexOutOfBoundsException var2) {
                throw new ConcurrentModificationException();
            }
        }
    }
}複製代碼

注視我都寫在代碼裏面了,其實ArrayList.Iterator 就是一個對數組的遍歷,較之直接 for()循環ArrayList,優勢是作了 fail-fast 檢查,而且增長了在遍歷過程當中刪除的功能。

再來詳細講一下for()循環裏面不能用list.remove(i)的緣由。由於在 for(int i=0; i<list.size();i++) 語句中,假設 list 有4個元素,假若是在 i = 0 的時候調用了list.remove(i),此時就出現了取值錯位而且漏值的狀況。可是在Iterator 裏面,咱們能夠看到有一行這樣的代碼 this.cursor = this.lastRet 改變了當前數組的角標。

這裏分享一個使用for循環而後再在循環裏面刪除值而且不會出錯的辦法

for (int i = list.size()-1; i >=0; i--) {
    String s = list.get(i);
    if (s.equals("remove")) {
        list.remove(i);
    }
}複製代碼

好了,很簡單的邏輯,Iterator 就分享到這裏吧,不一樣的集合裏面的 Iterator 的實現方式不同,可是邏輯都是同樣的,因此就再也不贅述了。

古耐~

相關文章
相關標籤/搜索