1. 程式人生 > >ArrayList原始碼學習筆記------常用API

ArrayList原始碼學習筆記------常用API

本文的原始碼來自於jdk1.8版本,然而並不會涉及jdk8新特性。

ArrayList簡介

ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。

ArrayList不是執行緒安全的,只能用在單執行緒環境下,多執行緒環境下可以考慮用Collections.synchronizedList(List l)函式返回一個執行緒安全的ArrayList類,也可以使用concurrent併發包下的CopyOnWriteArrayList類。

ArrayList實現了Serializable介面,因此它支援序列化,能夠通過序列化傳輸,實現了RandomAccess介面,支援快速隨機訪問,實際上就是通過下標序號進行快速訪問,實現了Cloneable介面,能被克隆。

ArrayList原始碼解析

在進行原始碼解析之前需要有以下的準備

1、transient關鍵字的作用
答:我們都知道一個物件只要實現了Serilizable介面,這個物件就可以被序列化,java的這種序列化模式為開發者提供了很多便利,我們可以不必關係具體序列化的過程,只要這個類實現了Serilizable介面,這個類的所有屬性和方法都會自動序列化。
然而在實際開發過程中,我們常常會遇到這樣的問題,這個類的有些屬性需要序列化,而其他屬性不需要被序列化,打個比方,如果一個使用者有一些敏感資訊(如密碼,銀行卡號等),為了安全起見,不希望在網路操作(主要涉及到序列化操作,本地序列化快取也適用)中被傳輸,這些資訊對應的變數就可以加上transient關鍵字。換句話說,這個欄位的生命週期僅存於呼叫者的記憶體中而不會寫到磁盤裡持久化。
總之,java 的transient關鍵字為我們提供了便利,你只需要實現Serilizable介面,將不需要序列化的屬性前新增關鍵字transient,序列化物件的時候,這個屬性就不會序列化到指定的目的地中。
2、原始碼中多次使用的Arrays類中的copyOf方法

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

該方法的作用是建立一個數組,並將original陣列中newLength個元素複製到新建立的陣列中。

ArrayList的成員變數

ArrayList的成員變數如下:

    /*定義集合底層陣列的預設容量為10*/
    private static final int DEFAULT_CAPACITY = 10;


    /*當用戶指定該 ArrayList 容量為 0 時,返回該空陣列 */
    private static final Object[] EMPTY_ELEMENTDATA = {};


    /*當用戶沒有指定 ArrayList 的容量時(即呼叫無參建構函式),返回的是該陣列*/
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};


    /*ArrayList基於陣列實現,用該陣列儲存資料, ArrayList 的容量就是該陣列的長度*/
    transient Object[] elementData;

    /*ArrayList中所包含元素的個數*/
    private int size;

DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個屬性貌似是jdk8才加入的,它和EMPTY_ELEMENTDATA的區別不大,只是兩者的使用情況不同。

ArrayList的建構函式

看程式碼可以發現ArrayList共有三個建構函式,其程式碼很易懂。

1、空參建構函式

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

2、建立集合時使用者指定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);
        }
    }

3、建立集合時使用者傳入一個指定的集合

public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                //將傳入的集合複製一份,並將其賦給elementData
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //若傳入的集合為空,則使用空陣列替代
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

常用API原始碼

1、add(E e)方法
ArrayList的add方法幾乎包含了ArrayList最主要的流程,其中包含了是否需要對陣列進行動態擴容的判斷,也包含了陣列動態擴容的具體方式。

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

ensureCapacityInternal(size + 1)方法的作用是在新增元素之前確定ArrayList的容量大小,其中size變數的值為當前ArrayList中的陣列的長度。這樣做的目的是用於判定本次新增元素的操作是否需要對底層的陣列進行擴容。

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    //如果當前是首次向陣列中新增元素,則返回當前所需要的最小容量和預設陣列容量的最大值
    //該值最終為本次新增元素操作所需要的最小陣列長度
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    //判定是否需要對陣列elementData進行擴容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

陣列的動態擴容

    //動態擴容的方法
    private void grow(int minCapacity) {
        //舊的陣列的長度
        int oldCapacity = elementData.length;
        //新的陣列的長度為舊陣列長度的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果舊陣列的長度依舊無法滿足要求,則使用minCapacity為新陣列的長度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果新陣列的長度超過了規定的陣列的最大長度(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)
        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);
    }

    private static int hugeCapacity(int minCapacity) {
        //minCapacity < 0,即minCapacity的值已經超出int型別的最大值
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

根據以上的學習可以總結出
1、ArrayList底層陣列的最大長度為int型別的長度。
2、ArrayList底層陣列在進行動態擴容時,陣列的長度每次增加1.5倍。
3、陣列新增單個元素的主要流程為,先計算出本次新增元素所需要的最小陣列長度x,再比較x與elementData.length的大小,當x的大小大於elementData.length的長度時,再對ArrayList底層陣列elementData進行擴容。

2、將元素插入到指定的所引處,public void add(int index,E element);

public void add(int index, E element) {
        //對指定的索引進行校驗
        rangeCheckForAdd(index);
        //確定陣列的長度
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //將原陣列index索引之前的元素保留,index所以之後的元素全體像後移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        //將element元素插入到陣列的index索引處
        elementData[index] = element;
        size++;
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

3、一次向ArrayList中新增一個集合public boolean addAll(Collection

public boolean addAll(Collection<? extends E> c) {
        //將集合轉成物件陣列
        Object[] a = c.toArray();
        int numNew = a.length;
        //確定陣列是否需要擴容
        ensureCapacityInternal(size + numNew);
        //陣列複製
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

4、在指定索引處新增集合public boolean addAll(int index, Collection

public boolean addAll(int index, Collection<? extends E> c) {
        //檢查索引index是否合法
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        //判斷是否需要進行陣列擴容
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //需要被移動的元素的數量
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                    numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

5、刪除指定索引處的元素 public E remove(int index)

public E remove(int index) {
        //檢查索引是否合法
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);
        //需要移動的元素的個數
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //將index+1索引之後的所有元素向前移動一位
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        //將最後一位的引用置空,等待GC回收,並將陣列的長度-1
        elementData[--size] = null; // clear to let GC do its work
        //將被刪除的值返回
        return oldValue;
    }

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

6、刪除指定的元素 public boolean remove(Object o),與上面的邏輯相同。

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)
            //將index+1索引之後的所有元素向前移動一位
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

7、刪除某個集合removeAll和retainAll兩個方法是相通的,區別在於removeAll(Collection

public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

觀察removeAll和retainAll的程式碼可發現,兩個方法的區別只是在呼叫刪除方法batchRemove時所傳的標誌位不同。batchRemove方法是根據方法第二個引數的值來判斷當前操作是進行刪除還是保留。

    /**
     * 該方法的主要邏輯為:
     * 遍歷陣列elementData,根據使用者傳入的判定條件,將符合條件的元素插入到elementData陣列的前面,
     * 最後再將其餘不符合規則的元素刪除。
     * @param c
     * @param complement
     * @return
     */
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        //r變數用於記錄遍歷elementData陣列的長度
        //w變數用於記錄符合條件(complement)的元素的索引
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                //將符合條件的元素插入到陣列的前面
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            //當c.contains(elementData[r])丟擲異常時執行
            if (r != size) {
                System.arraycopy(elementData, r,
                        elementData, w,
                        size - r);
                w += size - r;
            }
            //將符合條件的元素留下,將不符合條件的元素刪除
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

8、根據索引查詢 get(int index)

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

       return elementData(index);
}

9、據元素查詢其在集合中的索引 indexOf(Object o)和lastIndexOf(Object o)

//根據元素查詢其在集合中的索引(正向查詢),返回的是符合條件的第一個值的索引
   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;
       }
       //沒找到就返回-1
       return -1;
   }
//據元素查詢其在集合中的索引(反向查詢)
   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;
   }

10、修改指定索引處的元素 set(int index, E element)

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

        E oldValue = elementData(index);
        elementData[index] = element;
        //返回的是被替換的元素的值
        return oldValue;
}

11、其他的常用方法

public void clear() {
        modCount++;

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

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