1. 程式人生 > >【Java原始碼】基於陣列實現的ArrayList(上)

【Java原始碼】基於陣列實現的ArrayList(上)

    眾所周知,Java中ArrayList是基於陣列實現的

    咱們先看其基本屬性:

   
    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    transient Object[] elementData; 

    private int size;

  從上到下依次給出了

  大小為10 的初始大小

  為空的Object陣列

  預設為空的Object陣列

  不可序列化的Object陣列

  ArrayList的大小

 構造方法

  給定容量的構造方法

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

根據容量構造一個數組,若容量為負,丟擲異常

  無參構造方法

public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

  預設構造一個空陣列(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)

  根據已有的Collection構造ArrayList

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

  首先把collection轉化為陣列(利用Collection的toArray()方法)

  下面進行了一些判斷:

   判斷該陣列大小是否為0,如果為0則構造一個空陣列,不為0則繼續下面的判斷

               判斷該陣列的成員型別是否是Object,如果是則拷貝這個陣列(利用Arrays的copyOf(目標陣列,大小,陣列型別));                                                

  方法

   “修剪陣列” 即 去除多餘的(多申請的空間)

public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

  modCount應該是一個計數器,目前還不知道其作用。

  判斷size是否為0,是則直接返回一個空陣列,否則以大小為size拷貝陣列。

  ensureCapacity確保陣列容量

  其實最先看這個的時候並不知道他有什麼用,直到看到最後看到 明確容量擴容 的操作的時候,開始懷疑這個應該是在為新增做準備

public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

 引數為minCapacity,即最小容量

 先通過判斷陣列是否為空,得到minExpand(最小擴大),如果不為空,則預設minExpand = 10;

 minCapacity(最小容量)大於 minExpand(最小擴大),要進行ensureExplicitCapacity,確保明確的容量

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

  如果最小容量minCapacity大於陣列大小的話,進行

  grow擴容

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //Integer.MAX_VALUE = 0x7fffffff
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);
    }

  這裡可以看到grow函式將陣列大小擴大到其1.5倍;

  所以由此得出,新陣列容量必須  大於等於 原陣列大小的1.5倍,  接下來還要進行判斷

                如果最小容量minCapacity小於0,  異常       溢位!

  如果大於MAX_ARRAY_SIZE,就返回 Integer.MAX_VALUE

                否則返回 MAX_ARRAY_SIZE

  最後利用copyOf方法將elementData擴容。

private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

通過以上一系列 確保陣列容量 的操作,我們可以發現。ArrayList不是無限大的,其最大為 Integer.MAX_VALUE 即2147483647

 大小,是否為空,是否包含

public int size() {
        return size;
    }

public boolean isEmpty() {
        return size == 0;
    }

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

 這幾個很簡單,在這裡就不多做贅述了。

  indexOf 查詢某指定成員的第一個下標

public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

  引數:Object      分了三種情況,為空,能找到和找不到

  lastIndexOf 查詢某指定成員的最後一個下標

public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

  思路還是同上,不過這裡遍歷的順序是從尾部開始(逆序)

  clone拷貝

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

  這裡用到了Array.copyOf方法

  toArray 將ArrayList轉化為陣列   無參方法

public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

  同樣用到了Array.copyOf方法

  toArray 將ArrayList轉化為陣列   引數:一個數組

@SuppressWarnings("unchecked")
    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;
    }

 若引數陣列長度小於ArrayList長度, 則返回新陣列 = ArrayList陣列

                                            若大於, 則返回新陣列 = 引數陣列 (前ArrayList陣列長度為ArrayList元素,+ null + 剩下的為引數陣列對應下標元素)

目前並不是很清楚toArray有參方法存在的意義,自己認為這個null可能是個“分界點”

get 得到對應下標元素

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

        return elementData(index);
    }
    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

  返回對應下標元素,首先判斷了下標是否越界(rangeCheck(index)),越界則會丟擲異常,並輸出其Index和size

  奇怪的是,這裡並沒有判斷引數index是否為負,自己試了下,傳參為負時還是會報異常,不過沒有輸出,那為負時這裡的異常是如何產生的呢?

set 給對應下標賦值

    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

  還是先進行下標判斷(rangeCheck(index)),然後更改下標對應元素的值,並返回舊值

  add 增加  (引數為元素)

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

 add方法最重要的在他的第一步ensureCapacityInternal,確保足夠容量可以add進去

 然後進行ensureExplicitCapacity擴容,上面說過這個方法

add 增加  (引數為下標 + 元素)插入到下標元素前

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

同樣先判斷了下標的合法性,再確定了有足夠容量可以add

使用System.arraycopy(原陣列,原陣列開始下標,目標陣列,目標陣列開始下標,要copy陣列的長度)

這步的作用就是將index下標表示的元素及以後統統後移一個“長度”

eg:若index = 2 

before:0 1 2 3 4 5 6 

after:   0 1 2 2 3 4 5 6

剩下的步驟就顯而易見了。

remove 刪除 (引數:下標) 刪除指定下標的元素 

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

  還是先檢查下標的合法性,根據下標得到oldValue

  numMoved是計算得到的刪除元素後還有多少個元素

  再通過arraycopy將這些元素前移一個單位,--size,達到刪除目的

  remove 刪除 (引數:元素) 刪除指定元素 

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    private void fastRemove(int index) {
        modCount++;
        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
    }

  遍歷陣列刪除元素,核心在fastRemove方法

  其主要步驟同 remove(int index)

  clear  清空ArrayList

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

  遍歷清空

  未完待續。。。0(>_<)0