1. 程式人生 > 其它 >ArrayList原始碼分析(二)

ArrayList原始碼分析(二)

今天來進行ArrayList中關於迭代器方面的相關分析。

Iterator

初始化以及內部屬性詳情

    public Iterator<E> iterator() {
        return new 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 int expectedModCount = modCount; // prevent creating a synthetic constructor Itr() {} //其它程式碼 }

關於迭代器,可以從以上程式碼看出其內部並沒有定義實際的資料之類的,只定義了幾種方法,如cursor,lastRet,expectedModCount,用來提供迭代ArrayList物件的機制。

next方法

        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() { if (modCount != expectedModCount) throw new ConcurrentModificationException();    //當被併發修改時丟擲異常 }

next 方法是實際用來迭代的方法,返回迭代的元素,並且讓指標指向下一個元素。在進行一開始的檢測後(如上註釋所示),“if (i >= size)” 用來判斷是否迭代完成,之後的“if (i >= elementData.length)”,因為到這裡的前提是i < size,若滿足i >= elementData.length這個條件說明size > elementData.length,丟擲併發修改異常。之後cursor+1,將之前的cursor值賦給lastRet。

remove方法

 

public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

 

首先檢查lastRet是否小於0,lastRet小於0,要麼是還沒開始迭代時,為-1,要麼是已經呼叫過一次remove,會被再次設定為-1(後面程式碼就是)。這也可以發現,沒辦法連續兩次呼叫remove。接著檢查併發修改。之後呼叫ArrayList物件自己的remove方法來進行刪除,然後更新cursor和lastRet的值:cursor設定為lastRet,也就是自減了1,由於ArrayList的remove會把刪除元素後面的元素都往前移一位,所以cursor對應的元素仍然沒變。最後會設定迭代器的expectedModCount,因為ArrayList的remove會修改modCount值。所以我們發現,如果在用迭代器迭代元素時,想刪除元素,可以呼叫迭代器的remove方法,這不會導致後面的迭代丟擲異常;如果呼叫ArrayList的remove,會導致expectedModCount和modCount值不一致,迭代器就無法再使用了。  

forEachRemaining方法

public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i < size) {
                final Object[] es = elementData;
                if (i >= es.length)
                    throw new ConcurrentModificationException();
                for (; i < size && modCount == expectedModCount; i++)
                    action.accept(elementAt(es, i));
                // update once at end to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
        }

關於此部分程式碼,關鍵還是在for迴圈這塊,一開始將cursor賦給i,故此迴圈的範圍為cursor到size-1,迴圈中進行的操作為action.accept(elementAt(es, i)),我們可以點accept進去看下,其為Consumer函式式介面中的一個抽象方法。

我們可以這樣呼叫 forEachRemaining :iter.forEachRemaining(System.out::println);,這樣就可以快速的列印iter裡剩下的元素,其實是用方法引用來代替實現了Consumer介面的類的物件,也可以自行編寫lambda表示式,當然,也可以定義一個Consumer介面的實現類。

 

ListIterator

初始化以及內部屬性詳情

private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
}

public ListIterator<E> listIterator(int index) {
        rangeCheckForAdd(index);
        return new ListItr(index);
}

public ListIterator<E> listIterator() {
        return new ListItr(0);
}

如上所示,ListIterator根據指定的index進行迭代。

previous方法

public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
}

跟next方法大部分程式碼相同,本方法返回的是cursor指向元素的前一個元素,正如其名。

set方法

public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
}

set方法是用來進行替換操作的方法,要求lastRet >= 0,當然也有併發修改的相關的檢測。

add方法

public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
}

呼叫ArrayList自己的add之後,會讓cursor自增一個,由於add 會把index之後的元素都向後挪一位,所以cursor還是指向它之前指向的元素,最後設定expectedModCount。

SubList

SubList的執行結果是獲取ArrayList的一部分,返回的是ArrayList的部分檢視。

初始化以及內部屬性詳情

public List<E> subList(int fromIndex, int toIndex) {
     //fromIndex---擷取元素的起始位置,包含該索引位置元素
     //toIndex-----擷取元素的結束位置,不包含該索引位置元素 subListRangeCheck(fromIndex, toIndex, size);
return new SubList<>(this, fromIndex, toIndex); } private static class SubList<E> extends AbstractList<E> implements RandomAccess { private final ArrayList<E> root; private final SubList<E> parent; private final int offset; private int size; /** * 從ArrayList建立SubList. */ public SubList(ArrayList<E> root, int fromIndex, int toIndex) { this.root = root; this.parent = null; this.offset = fromIndex; this.size = toIndex - fromIndex; this.modCount = root.modCount; } /** * 從SubList再建立SubList. */ private SubList(SubList<E> parent, int fromIndex, int toIndex) { this.root = parent.root; this.parent = parent; this.offset = parent.offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = parent.modCount; }

由於SubList繼承了AbstractList類,所以它自己也有一個modCount屬性;可以選擇由ArrayList或者subList來建立一個新的subList。首先看由ArrayList初始化subList,畢竟你需要有第一個subList,才能用subList初始化出其它的subList。可以看到,這裡把root 設定為這個ArrayList物件本身,parent設定為null,然後分別設定offset,size和modCount。

再看由SubList初始化SubList,可以發現,root還是那個root,不管你subList嵌套了多少層,parent就是此subList上面一層,offset就是此subList相對於原始ArrayList的偏移量,層層疊加,size就是subList的長度,modCount和parent的保持一致。

get方法

public E get(int index) {
         Objects.checkIndex(index, size);
         checkForComodification();
         return root.elementData(offset + index);
}

get方法較為簡單,首先還是老樣子,進行相關檢測,如同步檢測,越界檢測,然後以offset + index作為索引的下標返回,offset相當於偏移量。

add方法

public void add(int index, E element) {
            rangeCheckForAdd(index);
            checkForComodification();
            root.add(offset + index, element);
            updateSizeAndModCount(1);
}
private void updateSizeAndModCount(int sizeChange) {    
SubList<E> slist = this;
   //改變此層及以上的size和modCount
do {
slist.size += sizeChange;
slist.modCount = root.modCount;
slist = slist.parent;
} while (slist != null);
}
 

該方法主要還是靠呼叫root的add即ArrayList本身的add的方法,主要還是看最後的updateSizeAndModCount(1),遞迴的改變這層和以上層的size和modCount,從這也可以發現,此層以下的subList就不管了,所以如果subList嵌套了許多層,需要用subList進行結構性修改的話,最好用最下面那層來改,不然,下面的subList就都廢掉了。

還有其他方法,但大都大同小異,就不具體分析了。最後關於迭代器中的for-each迴圈, 其實就是新建了一個迭代器,不斷進行hasNext()next()的呼叫。

就這樣吧,這些原始碼分析主要還是自用