jdk1.8集合源碼分析系列-1-ArrayList

接口繼承圖以及相關方法分析

先了解一下ArrayList整個接口繼承圖有哪些?
java

  • 看接口的時候,特別簡單的咱們沒必要贅述,有一些小的細節咱們會在方法上標記出來。
  • 另外父類接口和lambda方法會被省略掉。

Iterable

這裏封裝着迭代器通用的方法面試

public interface Iterable<T> {
    //返回一個T的迭代器
    Iterator<T> iterator();
    //其餘lambda的方法這裏省略掉
}
複製代碼

Collection

這裏封裝着一些集合通用的一些方法數組

public interface Collection<E> extends Iterable<E> {
    //----查詢操做----
    //返回集合長度
    int size();
    //集合爲空返回true
    boolean isEmpty();
    //集合包含指定元素返回true
    boolean contains(Object o);
    /**
     * 返回包含全部集合元素的數組,這個方法用於array和collection轉換,注意:
     * - 深複製出一個全新數組,不和以前的共享一個引用。
     * - 保證順序和以前的一致。
     */
    Object[] toArray();
    /**
     * 返回包含全部集合元素的數組,注意:
     * - 若是數組a大於集合長度,多餘的部分填充null
     */
    <T> T[] toArray(T[] a);

    //----修改操做----
    /**
     * 添加一個元素,注意:
     * - 這個方法有可能出現空指針,是和比較容易踩坑的方法,具體實現類若是不容許添加空元素的話。
     */
    boolean add(E e);
    boolean remove(Object o);


    //----塊操做----
    //當包含c集合全部元素返回true反之false。
    boolean containsAll(Collection<?> c);
    //添加集合全部元素,注意:這個方法很容易出現空指針,c要判空
    boolean addAll(Collection<? extends E> c);
    //差集操做,移除c集合存在的元素。注意:這個方法很容易出現空指針,c要判空
    boolean removeAll(Collection<?> c);
    //交集操做,保留集合和c集合都存在的元素。注意:這個方法很容易出現空指針,c要判空
    boolean retainAll(Collection<?> c);
    //清楚集合全部元素
    void clear();

    //----比較和哈希----
    //判斷兩個集合是否相等
    boolean equals(Object o);
    //得到hashCode
    int hashCode();
}
複製代碼

T[] toArray(T[] a);這個方法,咱們作一個小測試:安全

代碼以下: bash

測試結果:數據結構

List

這裏封裝着一些list通用的一些操做併發

public interface List<E> extends Collection<E> {  
    //在指定位置插入c集合全部元素
    boolean addAll(int index, Collection<? extends E> c);  
    //獲取指定位置的元素
    E get(int index);  
    //在指定位置替換元素。
    E set(int index, E element);  
    //在指定位置添加元素。
    void add(int index, E element);  
    //移除指定位置的元素。
    E remove(int index);  
    //獲取元素的第一次找到的位置下標,沒有返回-1。
    int indexOf(Object o);  
    //獲取元素的最後一次找到的位置下標,沒有返回-1。
    int lastIndexOf(Object o);  
    //返回list迭代器
    ListIterator<E> listIterator();  
    //返回index及之後元素的list迭代器
    ListIterator<E> listIterator(int index);  
    //返回從fromIndex到toIndex的視圖
    List<E> subList(int fromIndex, int toIndex);  
}  
複製代碼

Cloneable

這是一個標記接口,實現它表明你是否支持深複製。深複製和淺複製這裏不贅述,感興趣的同窗能夠搜索相關資料。咱們能夠經過調用其clone方法獲取一個深複製的實例。dom

public interface Cloneable {
}
複製代碼

RandomAccess

這也是一個標記接口,實現它標記你是否支持快速隨機訪問List中的元素。源碼分析

public interface RandomAccess {
}
複製代碼

AbstractCollection

這裏面主要把Collection接口中裏面一些通用的方法給實現了。測試

這裏有一個隱含約定俗稱的東西,咱們本身在作設計的時候AbstractXXX就表明對XXX作了一些通用邏輯的實現,不須要交給一堆的子類重複實現一遍。

這裏有一個小細節,最大數組長度:MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 之因此要-8是考慮到了一些虛擬機實現中在數組保留一下頭部信息。

AbstractList

這裏面主要把List接口中裏面一些通用的方法給實現了。

在Ideal裏面觀察AbstractList能夠發現增長了一個fieidmodCount,它表明着「結構性修改」的次數。但你使用迭代器(inerator或者listIterator)時,迭代器會保存modCount,遍歷每一個元素時都會校驗一次迭代器中modCount和List中modCount中是否一致,一旦不一致,就拋出ConcurrentModificationException異常。這就是fast-fail的策略。

ArrayList源碼分析

瞭解完接口了,下面咱們來開始瞭解ArrayList的代碼。

ArrayList數據結構:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默認初始化大小。具體怎麼初始化待會講
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空的數組實例,當須要返回空的數組實例時,都會返回這個實例。
     * 平時寫代碼也是鼓勵能複用的實例提早聲明,而後共享。減小無用new的次數。
     * 構造方法和trimSize都使用到了這個實例。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 也是一個空的數組,用於默認大小的空實例。
     * 可是它和EMPTY_ELEMENTDATA用途是不同的,它是用於肯定何時添加了第一個元素。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存放具體元素的地方。
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 用於保存集合的長度,這樣在進行size計算,就不須要重複計算一遍,直接讀取這個值。
     */
    private int size;

複製代碼

ArrayList是一個典型的線性表結構,這裏咱們簡單的回顧一下線性表結構和經常使用操做的複雜度。

ArrayList的重要方法

初始化

//jdk1.8的構造方法再也不默認構造一個10長度的示例,而是構造一個空的。那麼在何時擴容呢?答案是每一次add的時候
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//add方法實現以下
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
//而後咱們追溯到這個方法
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //注意這行判斷,DEFAULTCAPACITY_EMPTY_ELEMENTDATA的做用體現出來了,用於判斷何時第一次添加。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
}

//指定大小的構造咱們就沒必要說了。
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

複製代碼

擴容

//每次add(addAll)的時候會有一次內部容量確認的環節,代碼以下:
ensureCapacityInternal(size + 1);

//咱們來看下實現,當容量不夠可能致使溢出時,會調用grow方法進行擴容。
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//grow方法
private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //新容量擴大到原容量的1.5倍
        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);
    }

複製代碼

邊界檢測

ArrayList大多數讀寫方法都會調用邊界檢測邏輯,實現比較簡單,看一下就好了。

private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
複製代碼

ArrayList的源碼分析暫時就到這裏了,主要分析了一下面試中常常會問到的地方。

作一些關於ArrayList的題

arrayList是併發安全的麼?

顯然不是,好比數據結構中關鍵性的變量連可見性的保證都沒有,好比size等。更談不上併發安全了。

arrayList擴容策略是什麼?

1.8實現中,每次add類方法會作一次容量檢測,每次擴容1.5倍容量,同時會作邊界檢測。

相關文章
相關標籤/搜索