1. 程式人生 > 實用技巧 >《Java基礎知識》Java ArrayList原始碼分析

《Java基礎知識》Java ArrayList原始碼分析

前言

分析ArrayList 的原始碼為JDK8版本。

原始碼分析

我們先看看一個案例:

public class test2 {

    public static void main(String[] args) {
        int index = 10000000;
        ArrayList arrayList = new ArrayList();
        LinkedList linkedList = new LinkedList();
        long time0 =  System.currentTimeMillis();
        for (int i = 0; i < index ; i++) {
            arrayList.add(i);
        }
        
long time1 = System.currentTimeMillis(); System.out.println(time1 - time0); long time2 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { linkedList.add(i); } long time3 = System.currentTimeMillis(); System.out.println(time3
- time2); } }

執行結果:(多次執行結果之後發現不一定誰插入快)

第二種情況,給ArrayList 設定長度。

public class test2 {

    public static void main(String[] args) {
        int index = 10000000;
        ArrayList arrayList = new ArrayList(index);
        LinkedList linkedList = new LinkedList();
        long time0 =  System.currentTimeMillis();
        
for (int i = 0; i < index ; i++) { arrayList.add(i); } long time1 = System.currentTimeMillis(); System.out.println(time1 - time0); long time2 = System.currentTimeMillis(); for (int i = 0; i < index ; i++) { linkedList.add(i); } long time3 = System.currentTimeMillis(); System.out.println(time3 - time2); } }

執行結果:

無法得出結論誰插入快。

我們來看看為什麼ArrayList 插入也這麼快呢? 先來看看ArrayList 的原始碼

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

先分析ArrayList 的物件定義,發現繼承AbstractList,實現List<E>, RandomAccess, Cloneable, java.io.Serializable 四個介面。

AbstractList : 抽象類,定義了list的公共抽象方法。

List<E>: 介面, 定義了一些list的公共介面。

RandomAccess:標記介面,表示實現該介面的子類支援角標訪問,主要用於判斷list 是否實現該介面來知道是否可以通過下標訪問,做中介軟體非常常用。

Cloneable:標記介面,表示實現該介面的子類可以進行克隆:https://www.cnblogs.com/jssj/p/13767756.html

Serializable:標記介面,表示實現該介面的子類可以實現序列化和反序列化:https://www.cnblogs.com/jssj/p/11766027.html

接下來我們看看ArrayList的add方法的原始碼

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 判斷list的長度夠不夠,不夠就擴容
    elementData[size++] = e;           //elementData 是list真正儲存資料的陣列
    return true;
}

ok. 我們看看ArrayLIst是如何擴容的。

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {      // 這一步是判斷ArrayList是在建立物件的時候是否傳入指定長度。沒有傳入指定長度的.
            return Math.max(DEFAULT_CAPACITY, minCapacity);          // 則返回預設建立陣列的長度,為了後面可以直接擴容。
        }
        return minCapacity;
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)        //判斷當前陣列的長度是否夠用,不夠用,呼叫grow方法擴容。
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;                 //獲取原長度
        int newCapacity = oldCapacity + (oldCapacity >> 1);   //擴大1.5倍得到新長度。
        if (newCapacity - minCapacity < 0)                    //新獲得的長度是否小於要出入的位置,如果小,則直接擴大到需要插入位置的大小。(不知道什麼場景會進這裡)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)                 // 陣列的長度不能超過Integer 的長度-8. 8 指的是陣列本身也需要暫用的空間。
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);  // 複製陣列。真正的複製邏輯是native本地方法。
    }

指定位置新增元素:

    public void add(int index, E element) {
        rangeCheckForAdd(index);         // 判斷index是否有效

        ensureCapacityInternal(size + 1);  // 上面已經講過原理。
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);    // 從該位置複製一份後面的值,全部往後移。
        elementData[index] = element;      // 最後在當前位置修改元素值。
        size++;
    }

看看ArrayList 是如何刪除的:

    public E remove(int index) {
        rangeCheck(index);    // 判斷角標是否越界
        modCount++;           //操作計數器(用於迭代器迭代的時候如果這個list傳送變化,能夠及時感知到,提前報錯,而不是獲取錯誤資料)
        E oldValue = elementData(index);     

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);              // 從刪除位置開始到最後的值,全部向前移動。
        elementData[--size] = null;         // clear to let GC do its work  最後一位清空。 

        return oldValue;
    }

關於操作計數器(modCount),再通過案例說明一下

public class test2 {

    public static void main(String[] args) {
        int index = 100;
        ArrayList arrayList = new ArrayList(index);
        for (int i = 0; i < index ; i++) {
            arrayList.add(i);
        }

        Iterator<Integer> iterator = arrayList.iterator();
        while (iterator.hasNext()){
            int a =  iterator.next();
            if(a == 3){
                arrayList.remove(a);      // list 刪除了元素
            }
        }
    }
}

執行結果:

從案例中我們可以看到,迭代的過程中是不允許刪除或者新增元素的,修改沒有問題,要保證長度不變。

再來看一個案例

public class test2 {

    public static void main(String[] args) {
        long[] long1 = new long[]{1,2,3,5};
        List arrayList1 = Arrays.asList(long1);
        System.out.println(arrayList1.size());

        Long[] long2 = new Long[]{1l,2l,3l,5l};
        List arrayList2 = Arrays.asList(long2);
        System.out.println(arrayList2.size());
    }
}

執行結果:

我們要注意基本資料型別是不支援泛型化的。所以陣列轉list需要小心這種情況。

擴充套件知識,不可變集合

public class test2 {

    public static void main(String[] args) {
        //  不可變集合
        List list = Collections.unmodifiableList(Arrays.asList("2","5","7"));
        list.add("9");    // 該操作是不允許的
    }
}

執行結果:

總結

1. ArrayList 的底層資料結構使用的是陣列。

2. ArrayList是通過建立1.5倍長度的陣列來進行擴容的。

3.ArrayList被迭代的時候是不能改變原list的長度的。

4. 使用Arrays.asList 方法的時候需要注意陣列是否為基本型別。