集合框架2- ArrayList


其實 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);
    }
}

4. ArrayList 和 CopyOnWriteArrayList 的區別

CopyOnWriteArrayList 和 Vector 一樣也是線程安全的 List 。它在 java.util.concurrent 的包下。它的線程安全主要是通過讀寫分離來實現的。寫操作在一個復制的數組上實現(加鎖),讀操作還是在原數組中進行。

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

final void setArray(Object[] a) {
    array = a;
}

特點:適合讀多寫少的應用場景

缺點

  • 內存占用:在寫操作時需要復制一個新的數組,使得內存占用為原來的兩倍左右;
  • 數據不一致:讀操作不能讀取實時性的數據,因為部分寫操作的數據還未同步到讀數組中;

五、參考資料

ArrayList 擴容機制分析

Java ArrayList


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM