LinkedList和ArrayList底層數據結構及方法源碼說明

一、LinkedList(同時實現了List< E >接口和Deque< E > implements Queue< E >接口)
1.LinkedList底層數據結構是一個雙向鏈表(每個節點除了本身元素外,還包含了要指向的前一個節點Node< E > prev和後一個節點Node< E > next),雙向鏈表還記錄了頭節點Node< E > first和尾節點Node< E > last.
2.鏈表的數據結構在邏輯上是連續的,但是在物理空間上是不連續的(因此,索引下標和頭元素存放的物理內存地址是不相關的,所以不能根據索引下標直接獲取到元素,需要根據前後節點關係、索引值以及鏈表元素總個數size循環遍歷才能走訪到指定索引位置的元素);
3.LinkedList根據索引下標獲取元素get(index)方法的具體邏輯如下:

 public E get(int index) {
        checkElementIndex(index);//這個方法邏輯很簡單,就是判斷索引下標是否在鏈表長度範圍內,超出範圍則報IndexOutOfBoundsException
        return node(index).item;//重點關注這裏的node(index)方法採用了優化算法,先用index與鏈表長度size的一半比較(index < (size>>1)),若小於則從first-index,利用Node.next去一個個從前往後循環遍歷直到到達index所在位置;若大於則從last-index,利用Node.prev去一個個從後向前循環遍歷直到到達index所在位置.這樣可以減少一半的遍歷次數,性能有所提升.之所以可以採用這種優化算法,前提是在向鏈表中新增元素時,size加一和更新next/prev指向會同時執行,從而使得size\first\last之間通過next/prev的邏輯串聯起來,第一個索引下標元素first,那麼第二個索引下標元素就是first.next,最後一個索引下標元素last,那麼倒數第二個索引下標元素就是last.prev.
 }

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

        if (index < (size >> 1)) {//用index與鏈表長度size的一半比較,右移>>位運算
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;//從first開始,從前往後一個個遍歷直到到達index所在位置
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;//從last開始,從後向前一個個遍歷直到到達index所在位置
            return x;
        }
    }

4.LinkedList增加元素到指定索引下標add(index,Node< E > e)方法的具體邏輯如下:

/**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        checkPositionIndex(index);//這個方法邏輯很簡單,就是判斷索引下標是否在鏈表長度範圍內,超出範圍則報IndexOutOfBoundsException

        if (index == size)
            linkLast(element);//若index等於size,說明要在末位增加元素,直接調用末位增加元素的指定方法linkLast(element)即可,這樣可以避免走下面的邏輯,避免多繞彎兒
        else
            linkBefore(element, node(index));//該方法分兩部分:(1)查到指定index所在位置的元素Node<E> succ,然後再將succ.prev指向要增加的新元素element,這樣就在index位置插入了新元素.
    }
 /**
     * 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++;
    }

5.LinkedList在首尾增加元素時請務必直接使用addFirst(E e)和addLast(E e)方法,直接對first和last節點進行前prev後next指向的更新操作,這樣可以少走一些代碼邏輯,執行更快一些,removeFirst(Node< E > l)和removeLast(Node< E > l)也一樣.

6.LinkedList更新指定索引位置的元素值時,原理跟增刪一樣,都需要先根據index循環遍歷查找到index位置的節點Node< e >,然後再進行相關處理(節點前後指向的更新或節點元素值的更新)

 /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

7.由於Linked List 也實現了Deque< E > implements Queue< E >接口,所以具有以下相關方法:
(1)add VS offer(**注意:**LinkedList重寫的offer方法體裏是直接調用的add方法):
add()和offer()方法都可以往隊列末尾增加元素。當隊列已經到達最大容量後,再往裏面增加元素,add() 會拋出屬於非檢查異常的幾種RuntimeException(IllegalStateException/ClassCastException/NullPointerException/IllegalArgumentException),而offer返回false不拋出異常。
(2)element VS peek:
element() 和 peek()方法 都可以查詢隊列的首位元素。當隊列爲空時,element()方法會拋出異常,而 peek()返回null不拋出異常。
(3)remove VS poll:
remove() 和 poll() 方法都可以從刪除隊列的首位元素。當隊列爲空時,remove()方法會拋出異常,而 poll()返回null不拋出異常。

二、ArrayList
1.ArrayList底層數據結構是基於動態數組,與普通數組相比較而言,動態數據可以實現數組長度的自動擴容。由於是底層是數組結構(Object []),且實現了RandomAccess隨機存取標誌接口(表明實現這個這個接口的 List 集合是支持快速隨機訪問的。也就是說,實現了這個接口的集合是支持 快速隨機訪問策略,而且如果是實現了這個接口的 List,那麼使用for循環的方式獲取數據會優於用迭代器獲取數據);
2.所以其指定索引位置查詢get(index)的操作效率就比較高,時間複雜度爲O(1);

 /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);//此方法很簡單,檢查索引是否越界

        return elementData(index);//返回數組的指定位置的元素
    }
 // Positional Access Operations 位置訪問操作

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

3.但其在指定位置的增add(index,E element)刪remove(index)操作就性能就比較低,因爲會導致後續相關數組元素的位置移動(數據複製,調用System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length))),add(index,E element)可能會引發二次數組複製.
add(index,E element)

/**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!可能會引發數據擴容,進而可能出現數組複製(元素個數超過數組目前容量值時)
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//二次數組複製(若上面沒發生擴容,則此處爲首次數組複製)
        elementData[index] = element;
        size++;
    }

注意:當往數組add元素時,可能會引發數據擴容的相關操作,代碼邏輯如下:

(1)private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
   }
(2)private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
      return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
  }
(3)private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
      grow(minCapacity);
  }
(4)
/**
 * Increases the capacity to ensure that it can hold at least the
 * number of elements specified by the minimum capacity argument.
 *
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
  // overflow-conscious code
  int oldCapacity = elementData.length;
  int newCapacity = oldCapacity + (oldCapacity >> 1);//擴容爲舊容量的1.5倍
  if (newCapacity - minCapacity < 0)//若擴容爲原容量的1.5倍後依然不滿足要存的元素個數的要求(在往數組中添加集合addAll(Collection<? extends E> c)時可能會出現這種情況)
      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);
}

private static int hugeCapacity(int minCapacity) {
  if (minCapacity < 0) // overflow
      throw new OutOfMemoryError();
  return (minCapacity > MAX_ARRAY_SIZE) ?
      Integer.MAX_VALUE :
      MAX_ARRAY_SIZE;
}

remove(index)

/**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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);//數組複製,將數組中原先處於(index+1,index+1+numMoved)的這段元素複製到(index,index+numMoved),相當於這段元素(索引值都減了1)都往前移動了一位.
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

4.[指定刪除某個元素數據的方法remove(Object o)效率更低,因爲會先去循環遍歷找到元素所在索引位置,然後再去進行數組複製,多了一層循環操作];

/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)//從0開始在0-size範圍內循環遍歷
                if (elementData[index] == null) {//找到滿足條件的元素
                    fastRemove(index);//開始remove處理,很大可能會引發數組複製,除非要刪除的元素是最後一個
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)//從0開始在0-size範圍內循環遍歷
                if (o.equals(elementData[index])) {//找到滿足條件的元素
                    fastRemove(index);//開始remove處理,很大可能會引發數組複製,除非要刪除的元素是最後一個
                    return true;
                }
        }
        return false;
    }
   /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)//除非要刪除的元素非最後一個,否則會發生數組複製
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//數組複製,將數組中原先處於(index+1,index+1+numMoved)的這段元素複製到(index,index+numMoved),相當於這段元素(索引值都減了1)都往前移動了一位.
        elementData[--size] = null; // clear to let GC do its work
    }

5.而在尾部增add(E element)(不觸發擴容時不移動,觸發擴容的話就同在指定位置增add(int index,E element)一樣,也還是會調用native的System.arraycopy方法發生數組複製)刪remove(index)時不用移動。

 /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!可能會引發數據擴容,進而可能出現數組複製(元素個數超過數組目前容量值時)
        elementData[size++] = e;
        return true;
    }

6.更新指定索引位置的元素值set(int index,E element)需要先查找到該索引下的元素(因爲需要返回該舊值),然後再更新爲新元素,故其效率幾乎等同於查找指定索引位置元素的get(index)方法.

 /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章