Java學習——ArrayList的源碼解析

ArrayList是JAVA的一種泛型容器,它實際上是一個用數組實現的鏈表,今天解析一下ArrayList的源碼,並寫個簡單的實現。

ArrayList的依賴關係

首先我們來看ArrayList的依賴繼承關係:
在這裏插入圖片描述
通過ArrayList繼承實現的接口說明了該類具有以下特性:

  • ArrayList具有鏈表的特性
  • Collection接口表示ArrayList是一個數據集合,但是這個數據集合沒有位置和順序關係,因此ArrayList實現了類似isEmpty,contains的方法
  • RandomAccess接口說明了ArrayList支持隨機訪問,這是因爲底層是用數組實現,天然支持隨機訪問
  • Iterable接口表示ArrayList支持迭代訪問,可以使用foreach語法進行數據的遍歷
  • Serializable接口表示ArrayList的數據支持序列化
  • Cloneable接口表示ArrayList支持對象的克隆

ArrayList原理解析

ArrayList是通過數組實現,該類內部定義了一個Object數組用於存放數據,由於存放的數據類型是一個泛型,因此只能將數組定義爲Object[]類型,這是因爲Object類是所有類的父類,因此不管什麼類型的對象都能賦值給Object的變量。

 transient Object[] elementData;
 
 public ArrayList(int initialCapacity) 
 public ArrayList() 
 public ArrayList(Collection<? extends E> c)

ArrayList的構造函數有三種,如果沒有給定初始的數組大小,那麼將創建一個空的數組;如果給定數組初始大小,那麼將創建對應大小的數組;也支持給定一個Collection對象,那麼該對象內的數據將賦值到elementData數組內。

ArrayList數據的添加

ArrayList調用add方法添加數據時,會先檢查一下當前數組大小是否具有足夠的空間。

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

當數組空間不足時,最終會跳轉到grow方法進行數組擴容,該方法是計算原空間大小*1.5與minCapacity之間的最大值作爲新的數組的大小,然後將原數組的數據都拷貝至新的數組。

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

ArrayList數據的刪除

ArrayList的底層實現是數組,數據連續排放,所以在刪除數據時,效率較低,需要進行數據的挪動。

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        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;
    }
    

假設數組大小是size,要刪除的數據下標是index,那麼下標大於index的數據都要向左挪動一位,並將原來的最後一位置爲空。System.arraycopy執行的功能實際上就是進行數據的平移。

ArrayList轉數組

由於Array的底層實現是一個數組,因此,ArrayList可以將維護的數組返回,但是由於這是一個泛型容器,如果直接將底層的Object[]數組強制轉化爲T[]返回,編譯器會報錯,java不允許父類對象強轉爲子類變量。

 public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

toArray方法實現對具體類型數組的轉化,該方法有一個T[] a的參數,如果a的大小足夠存放所有數據,那麼直接進行數據的複製並返回結果。

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

如果a數組的大小不夠大,那麼需要創建一個新的T[]數組進行數據的複製。Array.newInstance(newType.getComponentType(), newLength)是利用了反射,通過給定的對象類型,創建一個指定大小,指定對象類型的數組,因爲創建的數組本身就是T[]類型的,因此這裏的強轉是沒有問題的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章