java提高篇(二一)-----ArrayList

來源:互聯網
上載者:User

標籤:

一、ArrayList概述

      ArrayList是實現List介面的動態數組,所謂動態就是它的大小是可變的。實現了所有可選列表操作,並允許包括 null 在內的所有元素。除了實現 List 介面外,此類還提供一些方法來操作內部用來儲存列表的數組的大小。

      每個ArrayList執行個體都有一個容量,該容量是指用來儲存列表元素的數組的大小。預設初始容量為10。隨著ArrayList中元素的增加,它的容量也會不斷的自動成長。在每次添加新的元素時,ArrayList都會檢查是否需要進行擴容操作,擴容操作帶來資料向新數組的重新拷貝,所以如果我們知道具體業務資料量,在構造ArrayList時可以給ArrayList指定一個初始容量,這樣就會減少擴容時資料的拷貝問題。當然在添加大量元素前,應用程式也可以使用ensureCapacity操作來增加ArrayList執行個體的容量,這可以減少遞增式再分配的數量。

      注意,ArrayList實現不是同步的。如果多個線程同時訪問一個ArrayList執行個體,而其中至少一個線程從結構上修改了列表,那麼它必須保持外部同步。所以為了保證同步,最好的辦法是在建立時完成,以防止意外對列表進行不同步的訪問:

        List list = Collections.synchronizedList(new ArrayList(...)); 
二、ArrayList源碼分析    

      ArrayList我們使用的實在是太多了,非常熟悉,所以在這裡將不介紹它的使用方法。ArrayList是實現List介面的,底層採用數組實現,所以它的操作基本上都是基於對數組的操作。

      2.1、底層使用數組

private transient Object[] elementData;

      transient??為java關鍵字,為變數修飾符,如果用transient聲明一個執行個體變數,當Object Storage Service時,它的值不需要維持。Java的serialization提供了一種持久化對象執行個體的機制。當持久化對象時,可能有一個特殊的對象資料成員,我們不想用serialization機制來儲存它。為了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。當一個對象被序列化的時候,transient型變數的值不包括在序列化的表示中,然而非transient型的變數是被包括進去的。

      這裡Object[] elementData,就是我們的ArrayList容器,下面介紹的基本操作都是基於該elementData變數來進行操作的。

      2.2、建構函式

     ArrayList提供了三個建構函式:

     ArrayList():預設建構函式,提供初始容量為10的空列表。

     ArrayList(int initialCapacity):構造一個具有指定初始容量的空列表。

     ArrayList(Collection<? extends E> c):構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。

/**     * 構造一個初始容量為 10 的空列表     */    public ArrayList() {        this(10);    }        /**     * 構造一個具有指定初始容量的空列表。     */    public ArrayList(int initialCapacity) {        super();        if (initialCapacity < 0)            throw new IllegalArgumentException("Illegal Capacity: "                    + initialCapacity);        this.elementData = new Object[initialCapacity];    }    /**     *  構造一個包含指定 collection 的元素的列表,這些元素是按照該 collection 的迭代器返回它們的順序排列的。     */    public ArrayList(Collection<? extends E> c) {        elementData = c.toArray();        size = elementData.length;        // c.toArray might (incorrectly) not return Object[] (see 6260652)        if (elementData.getClass() != Object[].class)            elementData = Arrays.copyOf(elementData, size, Object[].class);    }

      2.3、新增

      ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)這個五個方法來實現ArrayList增加。

     add(E e):將指定的元素添加到此列表的尾部。

public boolean add(E e) {    ensureCapacity(size + 1);  // Increments modCount!!    elementData[size++] = e;    return true;    }

       這裡ensureCapacity()方法是對ArrayList集合進行擴容操作,elementData(size++) = e,將列表末尾元素指向e。

     add(int index, E element):將指定的元素插入此列表中的指定位置。

public void add(int index, E element) {        //判斷索引位置是否正確        if (index > size || index < 0)            throw new IndexOutOfBoundsException(            "Index: "+index+", Size: "+size);        //擴容檢測        ensureCapacity(size+1);          /*         * 對源數組進行複製處理(位移),從index + 1到size-index。         * 主要目的就是空出index位置供資料插入,         * 即向右移動當前位於該位置的元素以及所有後續元素。          */        System.arraycopy(elementData, index, elementData, index + 1,                 size - index);        //在指定位置賦值        elementData[index] = element;        size++;        }

      在這個方法中最根本的方法就是System.arraycopy()方法,該方法的根本目的就是將index位置空出來以供新資料插入,這裡需要進行數組資料的右移,這是非常麻煩和耗時的,所以如果指定的資料集合需要進行大量插入(中間插入)操作,推薦使用LinkedList。

      addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素順序,將該 collection 中的所有元素添加到此列表的尾部。

public boolean addAll(Collection<? extends E> c) {        // 將集合C轉換成數組        Object[] a = c.toArray();        int numNew = a.length;        // 擴容處理,大小為size + numNew        ensureCapacity(size + numNew); // Increments modCount        System.arraycopy(a, 0, elementData, size, numNew);        size += numNew;        return numNew != 0;    }

      這個方法無非就是使用System.arraycopy()方法將C集合(先准換為數組)裡面的資料複製到elementData數組中。這裡就稍微介紹下System.arraycopy(),因為下面還將大量用到該方法。該方法的原型為:public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)。它的根本目的就是進行數組元素的複製。即從指定源數組中複製一個數組,複製從指定的位置開始,到目標數組的指定位置結束。將源數組src從srcPos位置開始複製到dest數組中,複製長度為length,資料從dest的destPos位置開始粘貼。

     addAll(int index, Collection<? extends E> c):從指定的位置開始,將指定 collection 中的所有元素插入到此列表中。

public boolean addAll(int index, Collection<? extends E> c) {        //判斷位置是否正確        if (index > size || index < 0)            throw new IndexOutOfBoundsException("Index: " + index + ", Size: "                    + size);        //轉換成數組        Object[] a = c.toArray();        int numNew = a.length;        //ArrayList容器擴容處理        ensureCapacity(size + numNew); // Increments modCount        //ArrayList容器數組向右移動的位置        int numMoved = size - index;        //如果移動位置大於0,則將ArrayList容器的資料向右移動numMoved個位置,確保增加的資料能夠增加        if (numMoved > 0)            System.arraycopy(elementData, index, elementData, index + numNew,                    numMoved);        //添加數組        System.arraycopy(a, 0, elementData, index, numNew);        //容器容量變大        size += numNew;           return numNew != 0;    }

      set(int index, E element):用指定的元素替代此列表中指定位置上的元素。

public E set(int index, E element) {        //檢測插入的位置是否越界        RangeCheck(index);        E oldValue = (E) elementData[index];        //替代        elementData[index] = element;        return oldValue;    }

      2.4、刪除

      ArrayList提供了remove(int index)、remove(Object o)、removeRange(int fromIndex, int toIndex)、removeAll()四個方法進行元素的刪除。

      remove(int index):移除此列表中指定位置上的元素。

public E remove(int index) {        //位置驗證        RangeCheck(index);        modCount++;        //需要刪除的元素        E oldValue = (E) elementData[index];           //向左移的位元        int numMoved = size - index - 1;        //若需要移動,則想左移動numMoved位        if (numMoved > 0)            System.arraycopy(elementData, index + 1, elementData, index,                    numMoved);        //置空最後一個元素        elementData[--size] = null; // Let gc do its work        return oldValue;    }

      remove(Object o):移除此列表中首次出現的指定元素(如果存在)。

public boolean remove(Object o) {        //因為ArrayList中允許存在null,所以需要進行null判斷        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;    }

      其中fastRemove()方法用於移除指定位置的元素。如下

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; // Let gc do its work    }

      removeRange(int fromIndex, int toIndex):移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之間的所有元素。

protected void removeRange(int fromIndex, int toIndex) {        modCount++;        int numMoved = size - toIndex;        System                .arraycopy(elementData, toIndex, elementData, fromIndex,                        numMoved);        // Let gc do its work        int newSize = size - (toIndex - fromIndex);        while (size != newSize)            elementData[--size] = null;    }

removeAll():是繼承自AbstractCollection的方法,ArrayList本身並沒有提供實現。

public boolean removeAll(Collection<?> c) {        boolean modified = false;        Iterator<?> e = iterator();        while (e.hasNext()) {            if (c.contains(e.next())) {                e.remove();                modified = true;            }        }        return modified;    }

      2.5、尋找

      ArrayList提供了get(int index)用讀取ArrayList中的元素。由於ArrayList是動態數組,所以我們完全可以根據下標來擷取ArrayList中的元素,而且速度還比較快,故ArrayList長於隨機訪問。

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

     2.6、擴容

      在上面的新增方法的源碼中我們發現每個方法中都存在這個方法:ensureCapacity(),該方法就是ArrayList的擴容方法。在前面就提過ArrayList每次新增元素時都會需要進行容量檢測判斷,若新增元素後元素的個數會超過ArrayList的容量,就會進行擴容操作來滿足新增元素的需求。所以當我們清楚知道業務資料量或者需要插入大量元素前,我可以使用ensureCapacity來手動增加ArrayList執行個體的容量,以減少遞增式再分配的數量。

public void ensureCapacity(int minCapacity) {        //修改計時器        modCount++;        //ArrayList容量大小        int oldCapacity = elementData.length;        /*         * 若當前需要的長度大於當前數組的長度時,進行擴容操作         */        if (minCapacity > oldCapacity) {            Object oldData[] = elementData;            //計算新的容量大小,為當前容量的1.5倍            int newCapacity = (oldCapacity * 3) / 2 + 1;            if (newCapacity < minCapacity)                newCapacity = minCapacity;            //數組拷貝,產生新的數組            elementData = Arrays.copyOf(elementData, newCapacity);        }    }

      在這裡有一個疑問,為什麼每次擴容處理會是1.5倍,而不是2.5、3、4倍呢?通過google尋找,發現1.5倍的擴容是最好的倍數。因為一次性擴容太大(例如2.5倍)可能會浪費更多的記憶體(1.5倍最多浪費33%,而2.5被最多會浪費60%,3.5倍則會浪費71%……)。但是一次性擴容太小,需要多次對數組重新分配記憶體,對效能消耗比較嚴重。所以1.5倍剛剛好,既能滿足效能需求,也不會造成很大的記憶體消耗。

      處理這個ensureCapacity()這個擴容數組外,ArrayList還給我們提供了將底層數組的容量調整為當前列表儲存的實際元素的大小的功能。它可以通過trimToSize()方法來實現。該方法可以最小化ArrayList執行個體的儲存量。

public void trimToSize() {        modCount++;        int oldCapacity = elementData.length;        if (size < oldCapacity) {            elementData = Arrays.copyOf(elementData, size);        }    }

 

      關注我的新站:CMSBLOGS.com

java提高篇(二一)-----ArrayList

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.