Java List 常用集合 ArrayList、LinkedList、Vector

低吟不作語發表於2020-09-23

Java 中的 List 是非常常用的資料型別。List 是有序的 Collection,Java List 一共有三個實現類,分別是:ArrayList、Vector、LinkedList



本文分析基於 JDK8


ArrayList

ArrayList 繼承自 AbstractList,實現了 List 介面。底層基於陣列實現容量大小動態變化,初始容量為 10,允許值為 null,有序,非執行緒安全,擅長隨機訪問

ArrayList 還實現了 RandomAccess、Cloneable、Serializable 介面,所以 ArrayList 是支援快速訪問、複製、序列化的

  • RandomAccess

    標記介面,用來表明其支援快速隨機訪問。如果是實現了這個介面的 List,那麼使用 for 迴圈的方式獲取資料會優於用迭代器獲取資料

  • Serializable

    標記該類支援序列化

  • Cloneable

    允許在堆中克隆出一塊和原物件一樣的物件,並將這個物件的地址賦予新的引用。ArrayList 提供的是一種深克隆機制,即克隆除自身物件以外的所有物件,包括自身所包含的所有物件例項。實現方式是先呼叫 super.clone() 方法克隆出一個新物件,然後再手動將原陣列中的值複製到一個新的陣列,並賦值


ArrayList 擴容機制

擴容機制應該是面試中最常問的了。其他關於 ArrayList 的一些瑣碎方法我就不細說了,主要介紹一下擴容機制。首先了解一下 ArrayList 的成員屬性

// 表示 ArrayList 的預設容量大小
private static final int DEFAULT_CAPACITY = 10;
// 一個空的 Object 陣列物件,長度為 0,如果使用預設建構函式建立,則 elementData 預設是該值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// ArrayList 中存放的實際元素個數
private int size;
// 當前元素物件存放的陣列,不參與序列化
transient Object[] elementData;

執行 add 方法時,會先執行 ensureCapacityInternal 方法,判斷當前陣列容量是否足夠,不夠就擴容。然後將待新增元素加到 elementData 末尾

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) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // minCapacity > elementData.length 則擴容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

再分析一下 ensureCapacityInternal 方法,此時 minCapacity 是 size + 1,這裡有兩個巢狀方法 calculateCapacity 和 ensureExplicitCapacity,作用分別如下:

  • calculateCapacity

    如果當前陣列為空,則先設定容量為預設值 10,此時還未初始化陣列

  • ensureExplicitCapacity

    確認實際的容量,如果不夠就擴容,關鍵的擴容函式 grow 就在這裡

擴充套件陣列大小,首先將容量擴大為原來的 1.5 倍,如果陣列是空陣列,則將陣列初始化,預設容量為 10,如果不是,再判斷是否超出最大容量,超過直接賦予最大值,否則賦予新值,複製原陣列到新陣列

private void grow(int minCapacity) {
    // 擴容前的容量
    int oldCapacity = elementData.length;
    // oldCapacity 右移一位,等於除以二
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 擴容之後還是不夠,直接賦予新值
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 擴容之後超出最大容量,直接賦予最大值
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 複製原陣列的值到新陣列
    elementData = Arrays.copyOf(elementData, newCapacity);
}

LinkedList

LinkedList 繼承自 AbstractSequentialList,實現了 List 和 Deque 介面,基於雙向連結串列實現,每個節點都包含了對前一個和後一個元素的引用,可以被當作堆疊、佇列或雙端佇列進行操作,有序,非執行緒安全

// 指向連結串列的第一個節點
transient Node<E> first;
// 指向連結串列的最後一個節點
transient Node<E> last;

JDK8 中 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 不存在容量不足的問題,克隆函式也是將所有元素全都克隆到新的 LinkedList 物件


Vector

Vector 是一個向量佇列,和 ArrayList 類似,繼承自 AbstractList,實現了 List 介面,就連額外介面也是一樣。不同之處在於:

  • Vector 使用 synchronized 保證執行緒同步
  • Vector 中遺留了大量傳統的方法,這些方法不屬於集合框架

Vector 有四個構造方法

// 建立一個預設大小為 10 的向量
public Vector()
// 建立指定大小的向量
public Vector(int initialCapacity)
// 建立指定大小的向量,並且指定增量。增量表示向量每次增加的元素數目
public Vector(int initialCapacity, int capacityIncrement)
// 建立一個包含集合 c 元素的向量
public Vector(Collection<? extends E> c)

Vector 的資料結構和 ArrayList 差不多,它包含了三個成員變數:

// 存放元素的動態陣列
protected Object[] elementData;
// 動態陣列的實際大小
protected int elementCount;
// 動態陣列的增長係數
protected int capacityIncrement;

隨著 Vector 中元素的增加,Vector 的容量也會動態增長,capacityIncrement 是與容量增長相關的增長係數,具體增長細節在 grow 函式中,和 ArrayList 類似

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 如果 capacityIncrement > 0,新的容量大小 = 舊的容量大小 + 增長係數
    // 否則容量擴大為原來的兩倍
    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);
}

Stack

Stack 是 Vector 的子類,實現了一個標準的後進先出的棧。Stack 也是通過陣列實現的,當然了,我們也可以將 LinkedList 當作棧來使用

Stack 只定義了預設建構函式,用來建立一個空棧

public Stack()

Stack 除了具有 Vector 的所有 API,還有自己實現的方法

// 判斷堆疊是否為空
public boolean empty()
// 檢視堆疊頂部的物件,但不從堆疊中移除它
public synchronized E peek()
// 移除堆疊頂部的物件,並作為此函式的值返回該物件
public synchronized E pop()
// 把物件壓入堆疊頂部
public E push(E item)
// 返回物件在堆疊中的位置,以 1 為基數
public synchronized int search(Object o)

Stack 的擴容機制基於 Vector,不過由於沒有指定增長係數,所有預設為 0,每次擴容陣列長度增大為原來的兩倍


相關文章