Java ArrayList 詳解


只記錄目前為止關注的。JDK1.8

一、基礎屬性

1.1 內部參數

    //空存儲實例。直接new ArrayList()便是以該空數組作為實例
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //默認容量大小,在由空實例進行首次擴容時,擴到到該長度。
    //實際使用中,並未實際存在Capacity這個參數,需要擴容時直接根據舊數組的length進行擴容
    private static final int DEFAULT_CAPACITY = 10;

    //實際存儲數組
    transient Object[] elementData;

    //存儲元素個數
    private int size;

    //該字段用於迭代器的fail-fast機制【參考另一篇文章】
    protected transient int modCount = 0;

1.2 三個重載構造方法

    //構建一個空實例,elementData指向容量為0的空數組
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    //以給定容量初始化存儲數組
    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);
        }
    }

    //以集合初始化創建列表
    //步驟:1. 調用toArray(),將給定集合轉成數組(以ArrayList為例,toArray()返回的是實際存在元素的那部分數組,即[0,size))
    //2. 讓size直接等於給定集合的數組長度
    //3. 判斷size如果為0則直接創建空存儲實例,否則使用Arrays.copyOf將給定集合數組復制一份(淺拷貝),作為存儲數組
    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;
        }
    }

    // ArrayList 的 toArray()源碼,復制[0,size)部分返回
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

二、操作及策略

2.1 動態擴容

擴容策略:當數組全滿了才擴容,新長度=舊長度*1.5

動態擴容有兩個入口:供用戶調用的顯式擴容ensureCapacity()和添加元素時的隱式擴容ensureCapacityInternal(),不過均是調用ensureExplicitCapacity()來根據傳入所需容量值決定是否擴容,最終實際擴容操作在grow()方法中。

    //顯式擴容入口方法
    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 值為 size+1,由add方法調用傳入
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    //添加方法
    public boolean add(E e) {
        // 傳入最小所需容量為size+1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }


    //計算所需容量,實則不需要計算
    //只是單純判斷當前是否是空實例,為空就話返回 "默認容量"與minCapacity之間的較大值,不為空直接返回minCapacity
    //參數 minCapacity 只有兩種情況:
    // 1. 隱式擴容時,如add()傳入,其值為 size+1
    // 2. 顯式擴容,用戶指定
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
    //在此處,根據最小所需容量來判斷是否實際進行擴容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0) //若已經占滿,才進行擴容
            grow(minCapacity);
    }

    //實際擴容方法
    //可以看到擴容策略為:length + length*0.5(右移1位縮小1倍)
    //然后調用Arrays.copyOf淺復制
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //舊長度+舊長度*2
        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);
    }

2.2 刪除操作

ArrayList中有兩個remove方法public E remove(int index)public boolean remove(Object o)

源碼實現得其實挺簡單,本來還在猶豫有沒有必要把remove方法記錄下來,但發現有個點值得注意以下:
注意點:JDK對於數組內元素統一移動操作,偏好使用System.arraycopy(ary, srcPos, ary, destPos, len)來移動


    //通過給定下標來刪除元素,並返回刪除元素
    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); // 使用System.arraycopyl來移動數組元素
        elementData[--size] = null; // clear to let GC do its work  // 刪除后size-1,並將先前最后一個元素置null

        return oldValue; //返回刪除元素
    }

    // 判斷下標是否合理(<size),否則拋出異常
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    // 刪除首個與給定元素相等的元素,元素判等使用equal方法
    // 成功返回true,失敗返回false
    public boolean remove(Object o) {
        if (o == null) { // 可以刪除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])) { // 從頭開始一個個檢查,使用equal判等
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * 官方注釋說得很清楚了:刪除元素,但跳過邊界檢查且不返回刪除元素。(就是remove(index)的縮略版
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    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
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM