第二章 ArrayList源碼解析

1、對於ArrayList須要掌握的七點內容java

  • ArrayList的建立:即構造器
  • 往ArrayList中添加對象:即add(E)方法
  • 獲取ArrayList中的單個對象:即get(int index)方法
  • 刪除ArrayList中的對象:即remove(E)方法
  • 遍歷ArrayList中的對象:即iterator,在實際中更經常使用的是加強型的for循環去作遍歷
  • 判斷對象是否存在於ArrayList中:contain(E)
  • ArrayList中對象的排序:主要取決於所採起的排序算法(之後講)

2、源碼分析算法

2.一、ArrayList的建立(常見的兩種方式)數組

        List<String> strList = new ArrayList<String>();
        List<String> strList2 = new ArrayList<String>(2);

ArrayList源代碼:安全

基本屬性:網絡

    //對象數組:ArrayList的底層數據結構
    private transient Object[] elementData;
    //elementData中已存放的元素的個數,注意:不是elementData的容量
    private int size;

注意:數據結構

  • transient關鍵字的做用:在採用Java默認的序列化機制的時候,被該關鍵字修飾的屬性不會被序列化。
  • ArrayList類實現了java.io.Serializable接口,即採用了Java默認的序列化機制
  • 上面的elementData屬性採用了transient來修飾,代表其不使用Java默認的序列化機制來實例化,可是該屬性是ArrayList的底層數據結構,在網絡傳輸中必定須要將其序列化,以後使用的時候還須要反序列化,那不採用Java默認的序列化機制,那採用什麼呢?直接翻到源碼的最下邊有兩個方法,發現ArrayList本身實現了序列化和反序列化的方法
        /**
         * Save the state of the <tt>ArrayList</tt> instance to a stream (that is,
         * serialize it).
         * 
         * @serialData The length of the array backing the <tt>ArrayList</tt>
         *             instance is emitted (int), followed by all of its elements
         *             (each an <tt>Object</tt>) in the proper order.
         */
        private void writeObject(java.io.ObjectOutputStream s)
                throws java.io.IOException {
            // Write out element count, and any hidden stuff
            int expectedModCount = modCount;
            s.defaultWriteObject();
    
            // Write out array length
            s.writeInt(elementData.length);
    
            // Write out all elements in the proper order.
            for (int i = 0; i < size; i++)
                s.writeObject(elementData[i]);
    
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
    
        }
    
        /**
         * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
         * deserialize it).
         */
        private void readObject(java.io.ObjectInputStream s)
                throws java.io.IOException, ClassNotFoundException {
            // Read in size, and any hidden stuff
            s.defaultReadObject();
    
            // Read in array length and allocate array
            int arrayLength = s.readInt();
            Object[] a = elementData = new Object[arrayLength];
    
            // Read in all elements in the proper order.
            for (int i = 0; i < size; i++)
                a[i] = s.readObject();
        }
    View Code

構造器:ide

    /**
     * 建立一個容量爲initialCapacity的空的(size==0)對象數組
     */
    public ArrayList(int initialCapacity) {
        super();//即父類protected AbstractList() {}
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity);
        this.elementData = new Object[initialCapacity];
    }

    /**
     * 默認初始化一個容量爲10的對象數組
     */
    public ArrayList() {
        this(10);//即上邊的public ArrayList(int initialCapacity){}構造器
    }

在咱們執行new ArrayList<String>()時,會調用上邊的無參構造器,創造一個容量爲10的對象數組。源碼分析

在咱們執行new ArrayList<String>(2)時,會調用上邊的public ArrayList(int initialCapacity),創造一個容量爲2的對象數組。this

注意:spa

  • 上邊有參構造器的super()方法是ArrayList父類AbstractList的構造方法,這個構造方法以下,是一個空構造方法:
        protected AbstractList() {
        }
  • 在實際使用中,若是咱們能對所需的ArrayList的大小進行判斷,有兩個好處:
    • 節省內存空間(eg.咱們只須要放置兩個元素到數組,new ArrayList<String>(2))
    • 避免數組擴容(下邊會講)引發的效率降低(eg.咱們只須要放置大約37個元素到數組,new ArrayList<String>(40))

2.二、往ArrayList中添加對象(常見的兩個方法add(E)和addAll(Collection<? extends E> c))

2.2.一、add(E)

strList2.add("hello");

ArrayList源代碼:

    /**
     * 向elementData中添加元素
     */
    public boolean add(E e) {
        ensureCapacity(size + 1);//確保對象數組elementData有足夠的容量,能夠將新加入的元素e加進去
        elementData[size++] = e;//加入新元素e,size加1
        return true;
    }
    /**
     * 確保數組的容量足夠存放新加入的元素,若不夠,要擴容
     */
    public void ensureCapacity(int minCapacity) {
        modCount++;
        int oldCapacity = elementData.length;//獲取數組大小(即數組的容量)
        //當數組滿了,又有新元素加入的時候,執行擴容邏輯
        if (minCapacity > oldCapacity) {
            Object oldData[] = elementData;
            int newCapacity = (oldCapacity * 3) / 2 + 1;//新容量爲舊容量的1.5倍+1
            if (newCapacity < minCapacity)//若是擴容後的新容量仍是沒有傳入的所需的最小容量大或等於(主要發生在addAll(Collection<? extends E> c)中)
                newCapacity = minCapacity;//新容量設爲最小容量
            elementData = Arrays.copyOf(elementData, newCapacity);//複製新容量
        }
    }

在上述代碼的擴容結束後,調用了Arrays.copyOf(elementData, newCapacity)方法,這個方法中:對於咱們這裏而言,先建立了一個新的容量爲newCapacity的對象數組,而後使用System.arraycopy()方法將舊的對象數組複製到新的對象數組中去了。

注意:

  • modCount變量用於在遍歷集合(iterator())時,檢測是否發生了add、remove操做。

2.2.二、addAll(Collection<? extends E> c)

使用方式:

        List<String> strList = new ArrayList<String>();
        strList.add("jigang");
        strList.add("nana");
        strList.add("nana2");
        
        List<String> strList2 = new ArrayList<String>(2);
        strList2.addAll(strList);

源代碼:

    /**
     * 將c所有加入elementData
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();//將c集合轉化爲對象數組a
        int numNew = a.length;//獲取a對象數組的容量
        ensureCapacity(size + numNew);//確保對象數組elementData有足夠的容量,能夠將新加入的a對象數組加進去
        System.arraycopy(a, 0, elementData, size, numNew);//將對象數組a拷貝到elementData中去
        size += numNew;//從新設置elementData中已加入的元素的個數
        return numNew != 0;//若加入的是空集合則返回false
    }

注意:

  • 從上述代碼能夠看出,若加入的c是空集合,則返回false
  • ensureCapacity(size + numNew);這個方法在上邊講
  • System.arraycopy()方法定義以下:
    public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos,  int length);

    將數組src從下標爲srcPos開始拷貝,一直拷貝length個元素到dest數組中,在dest數組中從destPos開始加入先的srcPos數組元素。

除了以上兩種經常使用的add方法外,還有以下兩種:

2.2.三、add(int index, E element)

    /**
     * 在特定位置(只能是已有元素的數組的特定位置)index插入元素E
     */
    public void add(int index, E element) {
        //檢查index是否在已有的數組中
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
        ensureCapacity(size + 1);//確保對象數組elementData有足夠的容量,能夠將新加入的元素e加進去
        System.arraycopy(elementData, index, elementData, index+1, size-index);//將index及其後邊的全部的元素整塊後移,空出index位置
        elementData[index] = element;//插入元素
        size++;//已有數組元素個數+1
    }

注意:

  • index<=size才行,並非index<elementData.length

2.2.四、set(int index, E element)

    /**
     * 更換特定位置index上的元素爲element,返回該位置上的舊值
     */
    public E set(int index, E element) {
        RangeCheck(index);//檢查索引範圍
        E oldValue = (E) elementData[index];//舊值
        elementData[index] = element;//該位置替換爲新值
        return oldValue;//返回舊值
    }

2.三、獲取ArrayList中的單個對象(get(int index))

實現方式:

        ArrayList<String> strList2 = new ArrayList<String>(2);
        strList2.add("hello");
        strList2.add("nana");
        strList2.add("nana2");
        System.out.println(strList2.get(0));

源代碼:

    /**
     * 按照索引查詢對象E
     */
    public E get(int index) {
        RangeCheck(index);//檢查索引範圍
        return (E) elementData[index];//返回元素,並將Object轉型爲E
    }
    /**
     * 檢查索引index是否超出size-1
     */
    private void RangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
    }

注:這裏對index進行了索引檢查,是爲了將異常內容寫的詳細一些而且將檢查的內容縮小(index<0||index>=size,注意這裏的size是已存儲元素的個數);

事實上不檢查也能夠,由於對於數組而言,若是index不知足要求(index<0||index>=length,注意這裏的length是數組的容量),都會直接拋出數組越界異常,而假設數組的length爲10,當前的size是2,你去計算array[9],這時候得出是null,這也是上邊get爲何減少檢查範圍的緣由。

 

2.四、刪除ArrayList中的對象

2.4.一、remove(Object o)

使用方式:

strList2.remove("hello");

源代碼:

 

    /**
     * 從前向後移除第一個出現的元素o
     */
    public boolean remove(Object o) {
        if (o == null) {//移除對象數組elementData中的第一個null
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {//移除對象數組elementData中的第一個o
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * 刪除單個位置的元素,是ArrayList的私有方法
     */
    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; //將最後一個元素設爲null,在下次gc的時候就會回收掉了
    }

 2.4.二、remove(int index)

使用方式:

strList2.remove(0);

源代碼:

    /**
     * 刪除指定索引index下的元素,返回被刪除的元素
     */
    public E remove(int index) {
        RangeCheck(index);//檢查索引範圍

        E oldValue = (E) elementData[index];//被刪除的元素
        fastRemove(index);
        return oldValue;
    }

注意:

  • remove(Object o)須要遍歷數組,remove(int index)不須要,只須要判斷索引符合範圍便可,因此,一般:後者效率更高。

 2.五、判斷對象是否存在於ArrayList中(contains(E)

源代碼:

    /**
     * 判斷動態數組是否包含元素o
     */
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    /**
     * 返回第一個出現的元素o的索引位置
     */
    public int indexOf(Object o) {
        if (o == null) {//返回第一個null的索引
            for (int i = 0; i < size; i++)
                if (elementData[i] == null)
                    return i;
        } else {//返回第一個o的索引
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;//若不包含,返回-1
    }

    /**
     * 返回最後一個出現的元素o的索引位置
     */
    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;
    }

注意:

  • indexOf(Object o)返回第一個出現的元素o的索引;lastIndexOf(Object o)返回最後一個o的索引

2.六、遍歷ArrayList中的對象(iterator())

使用方式:

        List<String> strList = new ArrayList<String>();
        strList.add("jigang");
        strList.add("nana");
        strList.add("nana2");
        
        Iterator<String> it = strList.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

源代碼:iterator()方法是在AbstractList中實現的,該方法返回AbstractList的一個內部類Itr對象

    public Iterator<E> iterator() {
        return new Itr();//返回一個內部類對象
    }

Itr:

    private class Itr implements Iterator<E> {
        
        int cursor = 0;//標記位:標記遍歷到哪個元素
        int expectedModCount = modCount;//標記位:用於判斷是否在遍歷的過程當中,是否發生了add、remove操做

        //檢測對象數組是否還有元素
        public boolean hasNext() {
            return cursor != size();//若是cursor==size,說明已經遍歷完了,上一次遍歷的是最後一個元素
        }

        //獲取元素
        public E next() {
            checkForComodification();//檢測在遍歷的過程當中,是否發生了add、remove操做
            try {
                E next = get(cursor++);
                return next;
            } catch (IndexOutOfBoundsException e) {//捕獲get(cursor++)方法的IndexOutOfBoundsException
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        //檢測在遍歷的過程當中,是否發生了add、remove等操做
        final void checkForComodification() {
            if (modCount != expectedModCount)//發生了add、remove操做,這個咱們能夠查看add等的源代碼,發現會出現modCount++
                throw new ConcurrentModificationException();
        }
    }

遍歷的整個流程結合"使用方式"與"Itr的註釋"來看。注:上述的Itr我去掉了一個此時用不到的方法和屬性。

3、總結

  • ArrayList基於數組方式實現,無容量的限制(會擴容)
  • 添加元素時可能要擴容(因此最好預判一下),刪除元素時不會減小容量(若但願減小容量,trimToSize()),刪除元素時,將刪除掉的位置元素置爲null,下次gc就會回收這些元素所佔的內存空間。
  • 線程不安全
  • add(int index, E element):添加元素到數組中指定位置的時候,須要將該位置及其後邊全部的元素都整塊向後複製一位
  • get(int index):獲取指定位置上的元素時,能夠經過索引直接獲取(O(1))
  • remove(Object o)須要遍歷數組
  • remove(int index)不須要遍歷數組,只需判斷index是否符合條件便可,效率比remove(Object o)高
  • contains(E)須要遍歷數組

作以上總結,主要是爲了與後邊的LinkedList做比較。

elementData
相關文章
相關標籤/搜索