Java 集合框架_ArrayList(源碼解析)

上一章分析了AbstractList抽樣類,這一章我們將全面分析最常用的集合ArrayList。

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

ArrayList繼承自AbstractList類,實(shí)現(xiàn)了List接口,RandomAccess這個(gè)可隨機(jī)訪問的標(biāo)記接口,Cloneable可克隆標(biāo)記接口,和Serializable可序列化標(biāo)記接口。

一.成員屬性

    // 默認(rèn)ArrayList集合的大小。
    private static final int DEFAULT_CAPACITY = 10;

    // 如果是空集合,那么都指向這個(gè)空數(shù)組常量,減少創(chuàng)建多個(gè)空數(shù)組變量。
    private static final Object[] EMPTY_ELEMENTDATA = {};

    // 如果是默認(rèn)集合,先用這個(gè)空數(shù)組常量代替,當(dāng)添加元素時(shí),再創(chuàng)建DEFAULT_CAPACITY長度的數(shù)組
    // 作用就是減少內(nèi)存使用。
    // 例如我們new 多個(gè)ArrayList集合時(shí),只要我們還沒有添加元素,它們內(nèi)部elementData都指向這個(gè)空數(shù)組
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    // 采用Object[]數(shù)組來存儲(chǔ)集合中的元素
    transient Object[] elementData;

    // 集合中元素的數(shù)量
    private int size;

它有兩個(gè)重要屬性,elementData Object數(shù)組用來存儲(chǔ)集合中元素,size表示集合中元素的數(shù)量。還有一個(gè)重要屬性就是繼承自AbstractList的modCount屬性。

二. 構(gòu)造函數(shù)

2.1 默認(rèn)構(gòu)造函數(shù)

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

注意這里和老版本不一樣,當(dāng)我們調(diào)用ArrayList空參構(gòu)造時(shí),它僅僅只是將空數(shù)組賦值給elementData,而不是創(chuàng)建一個(gè)DEFAULT_CAPACITY大小的新數(shù)組。當(dāng)我們真正調(diào)用添加元素方法時(shí),才會(huì)創(chuàng)建新數(shù)組。延遲創(chuàng)建默認(rèn)大小的數(shù)組。

2.2 設(shè)置數(shù)組初始長度的構(gòu)造函數(shù)

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

根據(jù)initialCapacity的值創(chuàng)建對(duì)應(yīng)大小的Object數(shù)組,如果小于0就拋出異常。

注意這里沒有使用延遲創(chuàng)建數(shù)組的方法,因?yàn)檫@里數(shù)組初始大小是由用戶自定義的。當(dāng)然也可以用一個(gè)成員變量記錄,然后延遲創(chuàng)建,只不過它這里沒有這么做。

2.3 通過Collection實(shí)例構(gòu)建集合

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray 返回的可能不是Object[]數(shù)組,所以進(jìn)行處理一下
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 如果c是一個(gè)空集合,那么這里就設(shè)置成EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

通過toArray方法,將集合c轉(zhuǎn)成數(shù)組,如果數(shù)組長度為0,那么就將 this.elementData設(shè)置成空數(shù)組常量EMPTY_ELEMENTDATA,而不是集合c轉(zhuǎn)成那個(gè)空數(shù)組,就可以讓這個(gè)新創(chuàng)建的空數(shù)組內(nèi)存釋放了。
還要注意一點(diǎn)就是,c.toArray()方法返回的可能并不是Object[]數(shù)組,所以這里做了判斷。

三. 重要方法

3.1 數(shù)組擴(kuò)容方法

我們知道使用數(shù)組中存放元素的大小是固定的,而ArrayList集合好像是無限大小的,但是ArrayList集合又是通過數(shù)組來實(shí)現(xiàn)的,所以它一定幫我們做了數(shù)組擴(kuò)容。

   public void ensureCapacity(int minCapacity) {
        // 如果是默認(rèn)構(gòu)造函數(shù)創(chuàng)建的ArrayList集合,那么它的最小數(shù)組長度都是DEFAULT_CAPACITY。
        // 如果不是的話,那么它最小數(shù)組長度就是0
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0
            : DEFAULT_CAPACITY;

        // 當(dāng)minCapacity > minExpand,那么就有可能要對(duì)數(shù)組進(jìn)行擴(kuò)容。
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

這個(gè)是供給外部調(diào)用的方法,來確保數(shù)組的長度大于這個(gè)minCapacity值。

    private void ensureCapacityInternal(int minCapacity) {
        // 如果相等,表示通過默認(rèn)構(gòu)造函數(shù)創(chuàng)建的ArrayList集合,而且還沒有創(chuàng)建新數(shù)組。
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            // 取minCapacity和DEFAULT_CAPACITY中較大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

這是供給類內(nèi)部使用的方法,在集合的添加元素方法中都會(huì)調(diào)用這個(gè)方法,確保數(shù)組長度不小于minCapacity值。

   private void ensureExplicitCapacity(int minCapacity) {
        modCount++; // 表示集合修改了

        // 當(dāng)數(shù)組長度小于minCapacity時(shí),就要進(jìn)行擴(kuò)容了
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

會(huì)調(diào)用grow方法,進(jìn)行數(shù)組擴(kuò)容操作。

    private void grow(int minCapacity) {
        // 獲取老數(shù)組長度
        int oldCapacity = elementData.length;
        // 先進(jìn)行1.5倍擴(kuò)容, oldCapacity >> 1 與oldCapacity/2 效果一樣,但是采用右移操作,速度比除法快得多。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果進(jìn)行擴(kuò)容之后,還是小于minCapacity值,那么就直接用minCapacity值當(dāng)成數(shù)組長度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 處理newCapacity值大于MAX_ARRAY_SIZE情況,集合容量也是有上限的。
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 使用Arrays.copyOf方法,將老數(shù)組的元素拷貝到newCapacity長度的新數(shù)組中,并返回這個(gè)新數(shù)組。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

進(jìn)行數(shù)組擴(kuò)容,先將新數(shù)組長度擴(kuò)充至老數(shù)組1.5倍,如果還是小于minCapacity,那么就直接使用minCapacity作為新數(shù)組長度,然后再處理超出最大數(shù)組長度問題。最后使用Arrays.copyOf方法,將老數(shù)組元素拷貝到新數(shù)組中。

3.2 添加元素

    public boolean add(E e) {
        // 確保數(shù)組容量
        ensureCapacityInternal(size + 1); 
        elementData[size++] = e;
        return true;
    }

在集合末尾添加元素,先確保數(shù)組容量,然后再在集合末尾添加元素,最后將集合數(shù)量size加一。

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

在集合指定索引位置添加元素。先檢查索引位置是否越界,確保數(shù)組容量滿足要求,然后調(diào)用System.arraycopy方法,將從索引位置開始的元素全部右移一位,再將索引位置設(shè)置成新元素element,最后集合數(shù)量size自增。

   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;
    }
  1. 將集合c轉(zhuǎn)成數(shù)組a,并得到數(shù)組長度numNew
  2. 確保集合數(shù)組長度不小于size + numNew
  3. 通過System.arraycopy方法,將數(shù)組a的全部元素拷貝到數(shù)組elementData的size位置之后,就是集合末尾。
  4. 將集合數(shù)量size加上numNew大小。
     public boolean addAll(int index, Collection<? extends E> c) {
        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;
    }
  1. 檢查索引位置是否越界。
  2. 將集合c轉(zhuǎn)成數(shù)組a,并得到數(shù)組長度numNew。
  3. 確保集合數(shù)組長度不小于size + numNew。
  4. numMoved 表示原數(shù)組要移動(dòng)元素的數(shù)量
  5. 將原數(shù)組index之后的元素(包括index),都向后移動(dòng)numNew個(gè)位置

我們要向一個(gè)數(shù)組從index位置插入numNew個(gè)元素,那么這個(gè)數(shù)組從index位置開始到結(jié)尾的都要移動(dòng)numNew個(gè)位置,一共移動(dòng)size - index個(gè)元素。

  1. 將數(shù)組a中全部元素插入到elementData數(shù)組index之后位置(包括index)。
  2. 將集合數(shù)量size加上numNew大小。

3.3 刪除元素

   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);
        // 清理引用,讓垃圾回收器可以回收無用的對(duì)象內(nèi)存
        elementData[--size] = null; 
    }

遍歷數(shù)組,找到與o相等的索引位置,然后調(diào)用fastRemove方法刪除這個(gè)索引位置的元素。
要?jiǎng)h除數(shù)組index位置元素,就是將index之后的元素都向前移動(dòng)一位,即使用System.arraycopy(elementData, index+1, elementData, index, numMoved)方法完成。

     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);
        // 清理引用,讓垃圾回收器可以回收無用的對(duì)象內(nèi)存
        elementData[--size] = null;
        return oldValue;
    }

先通過elementData(index)方法得到index位置的元素,便于返回這個(gè)被刪除的元素。然后通過System.arraycopy方法將數(shù)組index位置之后的元素都向前移動(dòng)一位,最后將數(shù)組最后位置的元素置位null,并將集合容量size自減。

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

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

移除與集合c相同元素,和只保留與集合c相同元素都調(diào)用batchRemove方法。

private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                // 如果complement為true,表示本集合只保留集合c中包含的元素
                // 如果complement為false,就刪除集合c中包含的元素,所以集合c中不包含的元素才保留到本集合中
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // 發(fā)生異常之后的補(bǔ)救處理
            // 如果r不等于size,表示數(shù)組沒有遍歷完成,將數(shù)組r位置之后的元素拷貝到數(shù)組w位置之后。
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                // w就表示現(xiàn)在集合的數(shù)量
                w += size - r;
            }
            // 如果不相等,說明本集合刪除元素了,就要進(jìn)行一些處理
            if (w != size) {
                // 將w位置之后的元素置位null,方便垃圾回收器回收。
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                // 將w的值賦值給size。
                size = w;
                modified = true;
            }
        }
        return modified;
    }

還記得我們?cè)贏bstractCollection類中是怎么操作的么?是調(diào)用迭代器的remove方法,但是在這里就不適用,因?yàn)閿?shù)組刪除一個(gè)元素會(huì)導(dǎo)致數(shù)組的移動(dòng),很浪費(fèi)時(shí)間。這里用了很巧妙的方法。

想一想我們平常怎么刪除數(shù)組中特定元素的。

  1. 第一種是新建一個(gè)長度一樣的數(shù)組,然后遍歷原數(shù)組,將滿足條件的元素才加入到新數(shù)組中,這樣就可以刪除不滿足條件的元素了。這種方式效率很快,但是需要多余的內(nèi)存。
  2. 第二種是反向遍歷數(shù)組,如果發(fā)現(xiàn)不滿足條件的元素,將這個(gè)位置之后的元素前移一位,這樣就覆蓋不滿足條件的元素了。這種方式不需要多余內(nèi)存,但是需要進(jìn)行數(shù)組的移動(dòng),耗費(fèi)時(shí)間。
  3. 第三種就采用非常巧妙的方法了,這里用到兩個(gè)位置索引r和w,用r索引來遍歷整個(gè)數(shù)組,用w表示保留下來元素的索引。所以遍歷數(shù)組,如果滿足條件,就將這個(gè)元素elementData[r]存放到elementData[w] w位置,并將w數(shù)量加一。因?yàn)閞一定大于或者等于w,所以不存在前面位置的元素覆蓋后面位置的元素。這種方式速度快,而且不用多余內(nèi)存。
   public void clear() {
        modCount++;
        // 清理引用,讓垃圾回收器可以回收無用的對(duì)象內(nèi)存
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

清除集合中元素。

protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // 清理引用,讓垃圾回收器可以回收無用的對(duì)象內(nèi)存
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

刪除集合中fromIndex位置到toIndex位置的元素(包括fromIndex,但是不包括toIndex)。通過 System.arraycopy方法將toIndex(包括toIndex位置)之后的元素全部向前移動(dòng)到fromIndex位置,所以toIndex位置的元素還是保留在集合中。然后將無效位置的元素全部置位null,方便垃圾回收器回收。

3.4 替換元素

    public E set(int index, E element) {
        rangeCheck(index);
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

就是將數(shù)組對(duì)應(yīng)位置替換成新元素element

3.5 查詢方法

    E elementData(int index) {
        return (E) elementData[index];
    }

返回對(duì)應(yīng)索引位置元素,并強(qiáng)轉(zhuǎn)成E類型。

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }

通過elementData方法獲取元素。

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

正向遍歷數(shù)組,查找與o相同元素,如果查找到就返回對(duì)應(yīng)下標(biāo)位置,如果沒有查到就返回-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;
    }

反向遍歷數(shù)組,查找與o相同元素,如果查找到就返回對(duì)應(yīng)下標(biāo)位置,如果沒有查到就返回-1。

    public Iterator<E> iterator() {
        return new Itr();
    }
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }
    public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

與AbstractList一樣,ArrayList提供兩個(gè)迭代器,都是ArrayList內(nèi)部類。

四. ArrayList的內(nèi)部類Itr

4.1 成員屬性

       // 光標(biāo)索引位置,0表示在集合開始位置(因?yàn)檫@是一個(gè)list集合,所以可以用索引表示開始位置)
        int cursor;
        // 表示讀取到的當(dāng)前元素位置
        int lastRet = -1;
        // 用來判斷原集合是否被修改,拋出ConcurrentModificationException異常。
        int expectedModCount = modCount;

4.2 遍歷集合

       final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

如果集合中modCount的值與迭代器中expectedModCount的值不相等,就說明在迭代器期間集合被修改了,那么遍歷的數(shù)據(jù)已經(jīng)失效,就拋出異常。

        public boolean hasNext() {
            return cursor != size;
        }

判斷集合中是否還有未讀取的元素,即cursor光標(biāo)已經(jīng)移動(dòng)到最后了。

       public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
  1. 先檢查集合有沒有沒被修改。
  2. 將cursor光標(biāo)位置賦值給變量i
  3. 然后判斷i位置是否有效
  4. 將cursor光標(biāo)位置加1,指向下一個(gè)元素。
  5. 將i賦值給lastRet,并返回i位置的元素,表示當(dāng)前遍歷到的元素。

4.3 刪除元素

     public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
  1. 如果lastRet < 0表示當(dāng)前位置元素?zé)o效,不能進(jìn)行刪除操作,拋出異常。
  2. 檢查集合有沒有沒被修改。
  3. 調(diào)用ArrayList集合remove(lastRet)方法刪除集合當(dāng)前元素。
  4. 將當(dāng)前元素位置索引lastRet賦值給cursor,然后將lastRet置位-1,表示當(dāng)前位置已失效。

注意這里的處理方法與AbstractList不一樣,但是實(shí)現(xiàn)的效果是一樣的。都是將cursor重置成當(dāng)前位置坐標(biāo)。

  1. 重新設(shè)置expectedModCount的值

五. ArrayList的內(nèi)部類 ListItr

它繼承自Itr類,實(shí)現(xiàn)了ListIterator接口。它實(shí)現(xiàn)了對(duì)List集合的反向遍歷,以及添加和替換集合中元素的方法。

5.1 構(gòu)造函數(shù)

        ListItr(int index) {
            super();
            cursor = index;
        }

表示從index - 1位置開始,反向遍歷集合元素。

5.2 反向遍歷集合

        public boolean hasPrevious() {
            return cursor != 0;
        }

判斷集合中是否還有未讀取的元素。當(dāng)cursor==0,表示已經(jīng)讀取到第一元素了,前面以及沒有元素了。

      public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }
  1. 先檢查集合有沒有沒被修改。
  2. 將cursor-1賦值給變量i
  3. 然后判斷i位置是否有效
  4. 將i的值賦值給cursor和lastRet;
  5. 并返回i位置的元素,表示當(dāng)前遍歷到的元素。

5.3 返回索引位置。

        // 如果是正向遍歷集合,nextIndex返回值表示集合中下一個(gè)元素的索引位置。
        // 如果是反向遍歷集合,nextIndex返回值表示集合中當(dāng)前元素的索引位置。
        public int nextIndex() {
            return cursor;
        }

        // 如果是正向遍歷集合,previousIndex返回值表示集合中當(dāng)前元素的索引位置。
        // 如果是反向遍歷集合,previousIndex返回值表示集合中前一個(gè)元素的索引位置。
        public int previousIndex() {
            return cursor-1;
        }

5.4 替換當(dāng)前元素

       public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
  1. lastRet < 0 表示當(dāng)前位置無效,不能更換元素,拋出異常。
  2. checkForComodification方法檢查集合有沒有沒被修改。
  3. 調(diào)用List集合的set(lastRet, e)方法,替換當(dāng)前元素。
  4. 因?yàn)樾薷牧思希敲粗匦沦x值expectedModCount = modCount

5.5 添加元素

       public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
  1. 調(diào)用checkForComodification方法檢查集合有沒有沒被修改。
  2. 調(diào)用List的add(i, e)方法,在cursor光標(biāo)位置插入元素。

那么就有兩種情況了,正向遍歷的時(shí)候,就是在當(dāng)前元素下一個(gè)索引位置插入,而反向遍歷時(shí),就是在當(dāng)前元素索引位置插入。

  1. lastRet = -1 設(shè)置當(dāng)前元素索引位置無效。
  2. cursor = i + 1 將光標(biāo)位置加1

這里就有問題了,它只能保證正向遍歷的時(shí)候,不會(huì)遍歷到剛剛插入的元素。但是反向遍歷的時(shí)候,因?yàn)閷ursor光標(biāo)位置加一,那么下次獲取前一個(gè)元素正好是剛剛添加的元素。

  1. 因?yàn)樾薷牧思希敲粗匦沦x值expectedModCount = modCount。

總結(jié)

ArrayList內(nèi)部是通過數(shù)組儲(chǔ)存元素的,數(shù)組的長度是固定,所以集合中元素?cái)?shù)量超出數(shù)組長度時(shí),要對(duì)數(shù)組進(jìn)行擴(kuò)容。

  • 一般先將長度擴(kuò)大到原數(shù)組長度的1.5倍。
  • 與集合元素?cái)?shù)量進(jìn)行比較,如果還是小于,就直接將元素?cái)?shù)量當(dāng)成數(shù)組長度。
  • 考慮最大上限問題。
  • 最后利用 Arrays.copyOf(elementData, newCapacity),創(chuàng)建一個(gè)新數(shù)組,將原數(shù)組元素都拷貝到新數(shù)組中,返回新數(shù)組賦值給elementData

數(shù)組擴(kuò)容操作一般都是在添加元素中判斷的,因?yàn)橹挥刑砑釉兀拍艹鲈瓟?shù)組儲(chǔ)存大小的情況。

添加元素

我們就以最復(fù)雜的一種情況為例,在索引index位置添加一個(gè)集合c。

  1. 檢查索引位置是否越界。
  2. 通過集合c的toArray方法,將集合轉(zhuǎn)成數(shù)組。
  3. 確保集合數(shù)組長度不小于本集合元素?cái)?shù)量與集合c元素?cái)?shù)量之和。(調(diào)用ensureCapacityInternal方法)
  4. 先通過System.arraycopy方法,將index位置之后的元素,全部右移集合c元素?cái)?shù)量的位數(shù)。

這樣就將index 到index+ numNew位置全部空出來,正好可以存放集合c全部元素了。

  1. 再使用System.arraycopy方法,將集合c中元素拷貝到本集合數(shù)組index 到index+ numNew位置。
  2. 最后將集合數(shù)量size加上numNew大小。

刪除元素

從源碼中看,ArrayList刪除元素分兩種情況。

  1. 刪除某一位置index的單個(gè)元素。就是通過System.arraycopy來實(shí)現(xiàn)index之后元素全部左移一位,實(shí)現(xiàn)了刪除效果。

記著將無效位置元素置位null,方便垃圾回收器回收內(nèi)存。

  1. 刪除多個(gè)元素。這里用了很巧妙的方法,具體請(qǐng)看文章中關(guān)于batchRemove(Collection<?> c, boolean complement)方法的詳解。

查詢方法

主要就是遍歷數(shù)組,很簡單。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,964評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,354評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,554評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,106評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,918評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,093評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,342評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評(píng)論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,839評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,107評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容