集合框架2- ArrayList

Ethan_Wong發表於2021-09-07

其實 Java 集合框架也叫做容器,主要由兩大介面派生而來,一個是 collection,主要存放物件的集合。另外一個是Map, 儲存著鍵值對(兩個物件)的對映表。

下面就來說說 List介面,List儲存的元素是有序、可重複的。其下有三個子介面,ArrayList、LinkedList 和 vector。

一、 ArrayList概述

ArrayList 底層資料結構是基於 Object 陣列來實現的,我們看看它的底層介面原始碼:

1. ArrayList 實現的介面

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

其中繼承的介面中的 RandomAccessCloneableSerializable 只是標記介面,他們的介面內部沒有具體的方法和引數:

public interface RandomAccess {} 
public interface Cloneable {}    //覆蓋clone(),能被克隆
public interface Serializable {} //支援序列化,能通過序列化傳輸

標記介面是電腦科學中的一種設計思路。程式語言本身不支援為類維護後設資料。而標記介面則彌補了這個功能上的缺失——一個類實現某個沒有任何方法的標記介面,實際上標記介面從某種意義上說就成為了這個類的後設資料之一。執行時,通過程式語言的反射機制,我們就可以在程式碼裡拿到這種後設資料。

以Serializable介面為例。一個類實現了這個介面,說明它可以被序列化。因此,我們實際上通過Serializable這個介面,給該類標記了“可被序列化”的後設資料,打上了“可被序列化”的標籤。這也是標記/標籤介面名字的由來。

此外AbstractList 繼承AbstractCollection 抽象類,實現List介面。它實現了 List 的一些基本操作如(get,set,add,remove),是第一實現隨機訪問方法的集合類,但是不支援新增和替換。

2.ArrayList 的成員屬性

private static final int DEFAULT_CAPACITY = 10; //預設初始容量為10 
private static final Object[] EMPTY_ELEMENTDATA = {}; //空陣列,用於空例項
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//用於預設大小空例項的共享空陣列例項。
transient Object[] elementData; //儲存ArrayList資料的陣列,transient修飾表示陣列預設不會被序列化
private int size; //ArrayList中陣列的個數

二、ArrayList 中的方法

Java ArrayList 中常用的方法:

方法 描述
add() 將元素插入到指定位置的 arraylist 中
addAll() 新增集合中的所有元素到 arraylist 中
clear() 刪除 arraylist 中的所有元素
clone() 複製一份 arraylist
contains() 判斷元素是否在 arraylist
get() 通過索引值獲取 arraylist 中的元素
indexOf() 返回 arraylist 中元素的索引值
removeAll() 刪除存在於指定集合中的 arraylist 裡的所有元素
remove() 刪除 arraylist 裡的單個元素
size() 返回 arraylist 裡元素數量
isEmpty() 判斷 arraylist 是否為空
subList() 擷取部分 arraylist 的元素
set() 替換 arraylist 中指定索引的元素
sort() 對 arraylist 元素進行排序
toArray() 將 arraylist 轉換為陣列
toString() 將 arraylist 轉換為字串
ensureCapacity() 設定指定容量大小的 arraylist
lastIndexOf() 返回指定元素在 arraylist 中最後一次出現的位置
retainAll() 保留 arraylist 中在指定集合中也存在的那些元素
containsAll() 檢視 arraylist 是否包含指定集合中的所有元素
trimToSize() 將 arraylist 中的容量調整為陣列中的元素個數
removeRange() 刪除 arraylist 中指定索引之間存在的元素
replaceAll() 將給定的操作內容替換掉陣列中每一個元素
removeIf() 刪除所有滿足特定條件的 arraylist 元素
forEach() 遍歷 arraylist 中每一個元素並執行特定操作

具體的方法細節可以看這裡

三、ArrayList 中的擴容機制

在初始化時,ArrayList 有三種方式來進行初始化,以無參構造方法建立 ArrayList 時,實際上賦值的是一個空陣列。當真正對陣列進行新增元素時,才真正的給 ArrayList 分配容量,即陣列容量擴為10。


/**
     *預設建構函式,使用初始容量10構造一個空列表(無引數構造)
     */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
     * 帶初始容量引數的建構函式。(使用者自己指定容量)
     */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {//初始容量大於0
        //建立initialCapacity大小的陣列
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {//初始容量等於0
        //建立空陣列
        this.elementData = EMPTY_ELEMENTDATA;
    } else {//初始容量小於0,丟擲異常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}


/**
    *構造包含指定collection元素的列表,這些元素利用該集合的迭代器按順序返回
    *如果指定的集合為null,throws NullPointerException。
    */
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)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}
/**
     * 將指定的元素追加到此陣列的末尾。
     */
public boolean add(E e) {
    //在增加元素前,先呼叫ensureCapacityInternal方法
    ensureCapacityInternal(size + 1);  
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    //比較當前元素個數和預設元素個數,如果小於10,則將最小容量設為預設值10
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //繼續呼叫 ensureExplicitCapacity()方法
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    //如果大於當前陣列預設長度,則進行擴容,呼叫grow()方法
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
/**
     * 要分配的最大陣列大小
     */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //在以前的容量基礎上增加舊容量的1/2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //檢查比較新容量與最小容量的大小,取大值
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //更新完新容量後,比較是否大於最大陣列大小 Integer.MAX_VALUE - 8
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        //若大於最大陣列大小,則呼叫hugeCapacity()方法
        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();
    //對minCapacity和MAX_ARRAY_SIZE進行比較
    //若minCapacity大,將Integer.MAX_VALUE作為新陣列的大小
    //若MAX_ARRAY_SIZE大,將MAX_ARRAY_SIZE作為新陣列的大小
    //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

四、ArrayList 相關面試題

1. System.arraycopy() 和 Arrays.copyOf() 的區別

聯絡:

看兩者原始碼可以發現 copyOf()內部實際呼叫了 System.arraycopy() 方法

區別:

arraycopy() 需要目標陣列,將原陣列拷貝到你自己定義的陣列裡或者原陣列,而且可以選擇拷貝的起點和長度以及放入新陣列中的位置 copyOf() 是系統自動在內部新建一個陣列,並返回該陣列。

2. ArrayList 和 LinkedList 的區別

  1. 是否保證執行緒安全: ArrayListLinkedList 都是不同步的,也就是不保證執行緒安全;
  2. 底層資料結構: Arraylist 底層使用的是 Object 陣列LinkedList 底層使用的是 雙向連結串列 資料結構(JDK1.6 之前為迴圈連結串列,JDK1.7 取消了迴圈。注意雙向連結串列和雙向迴圈連結串列的區別,下面有介紹到!)
  3. 插入和刪除是否受元素位置的影響:ArrayList 採用陣列儲存,所以插入和刪除元素的時間複雜度受元素位置的影響。 比如:執行add(E e)方法的時候, ArrayList 會預設在將指定的元素追加到此列表的末尾,這種情況時間複雜度就是 O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element))時間複雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之後的(n-i)個元素都要執行向後位/向前移一位的操作。 ② LinkedList 採用連結串列儲存,所以對於add(E e)方法的插入,刪除元素時間複雜度不受元素位置的影響,近似 O(1),如果是要在指定位置i插入和刪除元素的話((add(int index, E element)) 時間複雜度近似為o(n))因為需要先移動到指定位置再插入。
  4. 是否支援快速隨機訪問: LinkedList 不支援高效的隨機元素訪問,而 ArrayList 支援。快速隨機訪問就是通過元素的序號快速獲取元素物件(對應於get(int index)方法)。
  5. 記憶體空間佔用: ArrayList 的空 間浪費主要體現在在 list 列表的結尾會預留一定的容量空間,而 LinkedList 的空間花費則體現在它的每一個元素都需要消耗比 ArrayList 更多的空間(因為要存放直接後繼和直接前驅以及資料)。

3. ArrayList 和 Vector 的區別

  1. ArrayListList 的主要實現類,底層使用 Object[ ]儲存,適用於頻繁的查詢工作,執行緒不安全 ;
  2. VectorList 的古老實現類,底層使用 Object[ ]儲存,執行緒安全的。如下程式碼帶有 synchronized 同步鎖,能夠保證執行緒安全。
public synchronized void ensureCapacity(int minCapacity) {
    if (minCapacity > 0) {
        modCount++;
        ensureCapacityHelper(minCapacity);
    }
}

五、參考資料

ArrayList 擴容機制分析

Java ArrayList

相關文章