Vector、ArrayList、LinkedList的原理和區別?

Vector、ArrayList、LinkedList的原理和區別?

ArrayList

總體結構:
這裏寫圖片描述
先看源碼中ArrayList的Field:

    private static final long serialVersionUID = 8683452581122892189L;//用於序列化

    private static final int DEFAULT_CAPACITY = 10;//默認的數組大小

    private static final Object[] EMPTY_ELEMENTDATA = {};//空數組

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//默認空數組

    transient Object[] elementData; //數組緩存

    private int size;//大小,注意:和elementData的length是不一樣的,size只能小於等於length,因爲ArrayList經常不會存滿。

第一眼看到Object[] elementData,ArrayList的原理是基於數組的,而後面的LinkedList 是基於Node的一個雙向鏈表(待會會說。)
下面我們來分析一些ArrayList的主要方法:

ArrayList()

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;//如果明確指明大小爲0時,elementData就指向EMPTY_ELEMENTDATA
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//沒有指定大小時,elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    }

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)//這句話是判斷是否是Object類型的數組(比如int,long就不是Object類型的數組)
                elementData = Arrays.copyOf(elementData, size, Object[].class);//如果不是就需要轉成Object類型的數組
        } else {//如果集合爲空,則elementData指向EMPTY_ELEMENTDATA
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

add()

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //這一步就是去計算容量大小的方法。
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    //計算所需容量
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //當elementData爲空數組時,容量就爲DEFAULT_CAPACITY和minCapacity較大值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    //這裏需要說一下爲什麼要有一個DEFAULT_CAPACITY,因爲是爲了讓數組緩存在一定時間內夠用,
    //而不用反覆的重新創建適當容量大小的新數組緩存,因爲創建數組的開銷較大,效率較低,但是
    //DEFAULT_CAPACITY的值又不能過大,這樣會浪費內存空間。
    //所以DEFAULT_CAPACITY相當於一個域值,當不是認爲的指定了大小時,即使calculateCapacity()中指定了一個
    //minCapacity,也不能小於DEFAULT_CAPACITY的大小。

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code 這裏做了一個判斷,只有給定的minCapacity大於現在的容量時纔會grow,
        //否則沒有意義
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//這一步可以看出自動分配的新容量時原來的1.5倍
        if (newCapacity - minCapacity < 0)//但是當新容量仍然不能滿足時,就直接用指定的minCapacity
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//這裏就是重新創建一個新容量的數組
    }
    public E remove(int index) {
        rangeCheck(index);//檢查的方法,檢查不通過拋出異常用於中斷程序運行。

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//將後半截往前面錯一位。
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

從這個方法可以看出即使是刪除掉了一個元素,也不影響elementData的長度,也就是說內存空間並沒有變。

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

即使是清空,elementData在內存中所佔的長度仍然沒有變。

trimToSize()

刪除和清空操作都不能改變所佔內存,這是一件很操蛋的事情,下面這個方法就是釋放沒有使用的空間。

    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

Vector

總體結構:
這裏寫圖片描述
先看源碼中ArrayList的Field:

    protected Object[] elementData;//數組緩存

    protected int elementCount;//元素個數

    protected int capacityIncrement;//容量增長量

插:看了ArrayList的源碼,可以知道ArrayList的元素個數變量名字爲size,而這裏叫做elementCount,可以猜測是兩波人寫的東西。
果然,在JDK1.8中,ArrayList是Josh Bloch和 Neal Gafter,Vector是 Lee Boynton和Jonathan Payne

再一次看到Object[] elementData,我們就可以知道,和ArrayList一樣,Vector的原理是基於數組的,elementData就是用於存放數據的緩存。
下面我們來分析一些Vector的主要方法:

    //指定初始容量,和增長量的構造方法
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    } 
    //只指定初始容量的構造方法
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    //無參構造方法
    public Vector() {
        this(10);//重點:默認是10的大小
    }
    //指定集合的構造方法
    public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }

可以看到無參構造方法中,Vector默認給定的大小是10

最主要的我們來看一下grow()方法

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //重點
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

當capacityIncrement 大於0的時候,新的容量就等於老容量加上capacityIncrement ,如果capacityIncrement 小於等於0,新容量就是老容量的兩倍

Vector中的增刪改類型的方法前面均有synchronized關鍵字,說明Vector中的操作是線程同步的。

其實與ArrayList相比,Vector和ArrayList的關係更像是兄弟關係,父親完全一樣,只有自身的特點不一樣,增刪改的方法都差不多,只是Vector加了線程同步控制,這樣Vector的效率會低很多,但是是線程安全的。
另外grow()中,ArrayList是增長1.5倍,而Vector在沒有指定增長量的情況下默認增加2倍。

LinkedList

總體結構:

可以看出LinkedList 其實就比ArrayList 多實現了隊列的方法,而且是一個雙端隊列。
但是將LinkedList僅僅只看做是一個雙端隊列也不合理,因爲我們知道雙端隊列,兩個端口都可以入隊出隊,但是不能中間插入,但是LinkedList不僅可以兩端插入,刪除,還可以中間插入,這就是一個雙向鏈表的結構。

這裏寫圖片描述
詳細結構:
從樹形結構最頂部往下一次分析:

Iterable:(接口)

這裏寫圖片描述

forEach:

default void forEach(Consumer<? super T> action) { 
    //判斷是否爲null,是null拋出空指針異常。 
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
} 

這個接口最主要的就是定義了一個遍歷的方法。
default關鍵字可以讓接口中的方法可以有默認的函數體,這是jdk8的關鍵字,當一個類實現這個接口時,可以不用去實現這個方法,當然,這個類若實現這個方法,就等於子類覆蓋了這個方法,最終運行結果符合Java多態特性。

Collection:(接口)

這裏寫圖片描述

任何一個數據容器都應該包含幾類常用方法:

boolean add(E e):向集合添加元素
boolean addAll(Collection<? extends E> c)
刪
`boolean remove(Object o):`
`boolean removeAll(Collection<?> c)//集合刪除`
查
Iterator<E> iterator();//通過遍歷器去遍歷查詢每個元素
包含
boolean contains(Object o):判斷集合是否包含某個元素
boolean containsAll(Collection<?> c)
大小
int size():求集合大小
常用工具方法
boolean retainAll(Collection<?> c)//取交集  list1 與 list2 存在相同元素,list1集合只保留list2中存在的元素,list1 與 list3 不存在相同元素,list1集合變爲空
boolean isEmpty():判斷集合是否爲空
Object[] toArray():將集合中的元素轉換爲元素數組
<T> T[] toArray(T[] a):?
void clear()//清空
boolean equals(Object o)
int hashCode()

AbstractCollection(抽象類)

看到這個類前面有個Abstract,可以知道這是一個抽象類,那麼我們需要了解一下抽象類的作用,這會幫助你瞭解代碼設計的一些思想。
抽象類在我看來就一個作用:提供接口的骨架實現,以儘量減少實現此接口所需的工作量。

contains

public boolean contains(Object o) {
    Iterator<E> it = iterator();
    if (o==null) {
        while (it.hasNext())
            if (it.next()==null)
                return true;
    } else {
        while (it.hasNext())
            if (o.equals(it.next()))
                return true;
    }
    return false;
}

可以看出包含方法的原理是遍歷,一個一個去比較。
但是這裏需要注意的是,這裏比較是否包含的規則是,equals,也就是說不一定是內存地址相同。那麼equals規則可以由元素自己來確定。這種思想就是個人自掃門前雪。

toArray

public Object[] toArray() {
        // Estimate size of array; be prepared to see more or fewer elements
        Object[] r = new Object[size()];
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            // fewer elements than expected
            //這裏是一個控制,當實際元素個數少於預測個數時,直接重新分配一個適量的數組。
            if (! it.hasNext()) 
                return Arrays.copyOf(r, i);
            r[i] = it.next();
        }
        //當實際元素個數大於預測個數時,依然重新分配一個數組。
        return it.hasNext() ? finishToArray(r, it) : r;
    }

這裏需要理解的是:size()只是一個準備的大小,並不一定是真實大小。
如果不懂Arrays.copyOf(r, i)的使用可以參看我的這篇博客《Arrays的常用方法詳解》

add

public boolean add(E e) {
        throw new UnsupportedOperationException();
    }

這個方法更簡單,直接拋出一個不支持異常,所以這個方法是肯定需要子類重寫的。

addAll

public boolean addAll(Collection<? extends E> c) {
        boolean modified = false;
        for (E e : c)
            if (add(e))
                modified = true;
        return modified;
    }

這個方法定義了一個modified標誌,就是說只要有一個元素添加成功了,就說明源集合已經被修改過了,所以這裏可以看出addAll的返回值爲true時,並不是代表全部添加成功,而是代表源集合被修改過,或者說至少添加了一個元素進去

remove

public boolean remove(Object o) {
        Iterator<E> it = iterator();
        if (o==null) {
            while (it.hasNext()) {
                if (it.next()==null) {
                    it.remove();
                    return true;
                }
            }
        } else {
            while (it.hasNext()) {
                if (o.equals(it.next())) {
                    it.remove();
                    return true;
                }
            }
        }
        return false;
    }

可以看出刪除元素的原理還是遍歷,先找到這個元素,然後刪除這個元素

removeAll

public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<?> it = iterator();
        while (it.hasNext()) {
            if (c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }

如果理解了addAll,那麼removeAll也很好理解了,removeAll的返回值爲true的時候並不是代表全部刪除成功,而是表示至少刪除了一個元素

containsAll

如果此集合包含指定 集合中的所有元素,則返回true。
注意這裏是必須包含所有元素哦,也就是說只要有一個元素不包含就返回false,這也是下面源碼中算法理解

public boolean containsAll(Collection<?> c) {
        for (Object e : c)
            if (!contains(e))
                return false;
        return true;
    }

retainAll

public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }

retainAll是求交集的意思,原理其實就是在源集合中刪除不是交集的元素,也就是說這個方法會改變本集合的元素。
從源碼中可以看出,返回值如果爲true不代表求交集成功,而只能代表本集合有改變。

clear

public void clear() {
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            it.next();
            it.remove();
        }
    }

clear就很好理解了,就是遍歷,一個一個的刪除。

toString

public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {//無限循環,退出循環的控制在內部使用return來控制。
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

這裏可以看出原理是使用StringBuilder 來進行字符串的拼接,這裏之所以不用String來拼接,是因爲String的拼接效率會很慢,至於具體爲什麼,可以參看我的這篇博客《String、StringBuffer、StringBuilder 的區別?String爲什麼是不可變的?》

另外這裏有一個小技巧,就是無限循環的使用,我主要是想說一下我們一般在什麼時候使用無限循環。通常我們的循環都是在循環之前就已經知道循環次數,那麼如果我們在循環之前不知道具體的循環次數呢,這個時候就需要使用無限循環,退出循環的控制在循環體內部來控制。

List(接口)

在總體結構圖中可以看出,List接口集成Collection接口,說明List也是一種特殊的Collection,那麼其中Collection的方法我就不分析了,只是說一些List中定義的List獨有方法。

sort

default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

最終的排序還是使用的數組排序方法Arrays.sort(),讓後將排序號的數組,一個一個遍歷重新放置到list中。
Arrays.sort()的方法具體用法可以參看《Arrays的常用方法詳解》

既然是list就肯定有序號,那麼就會有序號的相關方法

E get(int index);

根據序號獲得元素

E set(int index, E element);

給某個位置設置元素

void add(int index, E element);

在某個位置後面插入一個元素

E remove(int index);

刪除某個位置的元素

int indexOf(Object o);

求某個元素的位置

int lastIndexOf(Object o);

求一個元素最後出現的位置

List subList(int fromIndex, int toIndex);

根據位置截取這個list,得到一個新的list

Queue(接口)

Queue是隊列的意思,也是繼承Collection接口的,說明隊列也是一種特殊的集合。
那麼隊列有哪些常見的操作呢?

boolean add(E e);

增加一個元索, 如果隊列已滿,則拋出一個IIIegaISlabEepeplian異常

boolean offer(E e);

添加一個元素並返回true, 如果隊列已滿,則返回false

E remove();

移除並返回隊列頭部的元素, 如果隊列爲空,則拋出一個NoSuchElementException異常

E poll();

移除並返問隊列頭部的元素,如果隊列爲空,則返回null

 E element();

返回隊列頭部的元素,如果隊列爲空,則拋出一個NoSuchElementException異常

 E peek();

返回隊列頭部的元素,如果隊列爲空,則返回null

Queue接口總結下來定義了6個方法,增刪查各兩個方法。都是一個方法拋異常,一個方法返回null。

AbstractList(抽象類)

這個抽象類繼承了AbstractCollection也實現了List接口的部分方法,剛剛我們說了抽象類的主要目的是實現一個骨架,那麼AbstractList骨架就是在AbstractCollection骨架的基礎上繼續添加一些新的方法,就是List接口中的部分方法。

indexOf

public int indexOf(Object o) {
        ListIterator<E> it = listIterator();
        if (o==null) {
            while (it.hasNext())
                if (it.next()==null)
                    return it.previousIndex();
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return it.previousIndex();
        }
        return -1;
    }

這段代碼其實也很好理解,但是有個地方我需要說明一下,就是it.next()這個方法,取得元素之後,指針會往後面一個元素移動,所以要獲取到前面一個元素的index就必須使用
previousIndex()方法。

lastIndexOf

public int lastIndexOf(Object o) {
        ListIterator<E> it = listIterator(size());
        if (o==null) {
            while (it.hasPrevious())
                if (it.previous()==null)
                    return it.nextIndex();
        } else {
            while (it.hasPrevious())
                if (o.equals(it.previous()))
                    return it.nextIndex();
        }
        return -1;
    }

這個方法其實和indexOf方法一樣,只不過一個是從前面開始往後面找,這個方法是反過來,從後面開始往前面找。
另外需要注意的是ListIterator是AbstractList裏的一個內部類,通過listIterator()方法來獲取這個內部類,這裏使用這個內部類的最大作用是爲其擴展一些方法。比如我們這裏擴展了hasPrevious的方法,實際上都是在對AbstractList中的元素進行操作,但是,說到擴展方法,爲什麼我們不直接將hasPrevious方法寫到AbstractList類裏面呢,因爲如果直接寫到AbstractList類裏面,會出現繼承關係混亂,臃腫,有些時候我們其實並不希望AbstractList的子類直接繼承到hasPrevious這個方法,這個時候就需要我們的內部類了,具體可以參看《Java內部類的作用》

subList

public List<E> subList(int fromIndex, int toIndex) {
        return (this instanceof RandomAccess ?
                new RandomAccessSubList<>(this, fromIndex, toIndex) :
                new SubList<>(this, fromIndex, toIndex));
    }

這個方法是最值得注意的一個方法,從名字上面看是截取這個list,但是我們看了SubList類之後我們會發現,其實我們真正操作的還是當前List,也就是說SubList只是對當前List的一個包裝,我們操作這個Sublist的數據的時候,當前list的數據也會改變。

Deque(接口)

雙端隊列
概念解釋:

deque 即雙端隊列。 (deque,全名double-ended queue)是一種具有隊列和棧的性質的數據結構。雙端隊列中的元素可以從兩端彈出,其限定插入和刪除操作在表的兩端進行
雙端隊列是限定插入和刪除操作在表的兩端進行的線性表。這兩端分別稱做端點1和端點2。也可像棧一樣,可以用一個鐵道轉軌網絡來比喻雙端隊列。在實際使用中,還可以有輸出受限的雙端隊列(即一個端點允許插入和刪除,另一個端點只允許插入的雙端隊列)和輸入受限的雙端隊列(即一個端點允許插入和刪除,另一個端點只允許刪除的雙端隊列)。而如果限定雙端隊列從某個端點插入的元素只能從該端點刪除,則該雙端隊列就蛻變爲兩個棧底相鄰的棧了。

增
void addFirst(E e);
void addLast(E e);

boolean offerFirst(E e);
boolean offerLast(E e);

刪
E removeFirst();
E removeLast();

E pollFirst();
E pollLast();

查
E getFirst();
E getLast();

E peekFirst();
E peekLast();

boolean removeFirstOccurrence(Object o);//刪除第一次出現在隊列中的這個元素
boolean removeLastOccurrence(Object o);//刪除最後一次出現在隊列中的這個元素

記憶:雙端隊列依舊是隊列,當然具備隊列的特質,入隊,出隊等,但是又由於雙端隊列體現了雙端的特點,所以就必定有一個頭(first)、一個尾(last)。

LinkedList

終於到了這個類,先看一下field

    transient int size = 0;//大小
    /**
     * Pointer to first node.//指向第一個節點
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.//指向第最後一個節點
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

Node是LinkedList中的一個內部類,用於輔助LinkedList。
我們來分析一下LinkedList這個名字,就是鏈表的意思,而且是個雙向鏈表,關於雙向鏈表的概念,我百度一個,大家參考一下:

雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。一般我們都構造雙向循環鏈表。

看到重點沒有?就是說每個節點裏面都有前一個節點和後一個節點的引用,這樣就可以把節點與節點關聯起來,看看Node的代碼:

private static class Node<E> {
        E item;
        Node<E> next;//指向後一個節點
        Node<E> prev;//指向前一個節點

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

好了,到這裏LinkedList的結構就說完了,其他的就是基於這個結構的一些方法了。這些方法都是接口中沒有實現的方法。這裏說幾個我覺得比較有學習價值的方法

其中最主要的就是linkFirst()、linkLast()、linkBefore()、unlinkFirst()、unlinkLast()、unlink()這六個方法。

linkFirst

    /**
     * Links e as first element.將元素鏈接到首端
     */
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);//構建頭結點
        first = newNode;
        //更改結點的鏈接
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

linkLast

    /**
     * Links e as last element.將元素鏈接到尾端
     */
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

linkBefore

    /**
     * 在succ結點前插入元素爲e的一個新的結點
     * Inserts element e before non-null Node succ.
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

unlinkFirst

斷開第一個節點鏈接

    /**
     * Unlinks non-null first node f.
     */
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC 幫助GC回收
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

get

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

想要獲得index位置的元素就必須要先獲得index位置的結點。node(index)

node

    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {//這一步是重點
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

這裏有一個重點,就是先判斷index是屬於前面半段還是後面半段,也就是先定位一個大概範圍,然後再遍歷查找

toArray

    public Object[] toArray() {
        Object[] result = new Object[size];
        int i = 0;
        for (Node<E> x = first; x != null; x = x.next)//重點
            result[i++] = x.item;
        return result;
    }

爲什麼把這個方法拿出來說,主要是說一下這個for循環,打破了我們常規的書寫方式,那是因爲我們以前都已經寫習慣了i++這種方式,其實for循環不僅僅是這種寫法,總結一下,只要有初始值,判斷條件,和控制語句,就可以組成循環。所以結構只要符合(初始語句;布爾值;控制語句)就可以實現循環。for(;;)這種情況就是無限循環。

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