1. 程式人生 > >thinking in java (十一) ----- 集合之ArrayList

thinking in java (十一) ----- 集合之ArrayList

ArrayList概述

ArrayList是實現了List介面的動態陣列,所謂的動態是其大小可變。允許包括null在內的元素。除了實現List介面外,此類還提供了一些方法來操控內部來儲存列表的陣列大小

每個ArrayList例項都有一個容量,該容量是指用來儲存元素的陣列大小。預設初始容量是10,隨著ArrayList元素的增加,它的容量也會一直增加。在每一次增加新的元素的時候,ArrayList都會檢查是否需要擴容,擴容操作帶來資料向新的陣列的COPY,所以我們如果知道具體業務數量(一般不能),在構造ArrayList的時候可以指定一個初始容量,這樣會減少擴容操作。

需要注意的是,ArrayList不是同步的,如果多執行緒訪問一個ArrayList例項,而其中至少一個執行緒從結構上改變了元素列表,那麼必須保持外部同步,為了同步,最好是在建立時完成同步操作:

List list = Collection。synchronizedList(new ArrayList(...));

ArrayList原始碼分析

  • 底層使用陣列實現
private static final int DEFAULT_CAPACITY = 10;
————————————————————
預設初始容量10
transient Object[] elementData;
+++++++++++++++++
沒必要持久化其值
  • 建構函式

ArrayList有三個建構函式

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

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

 public ArrayList(Collection<? extends E> c) :構造一個包含指定collection的元素的列表,

原始碼:


    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);
        }
    }
——————————————————————————
 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列表,一個有初始容量的空列表,一個指定collection元素的列表,

  • 新增(add)

ArrayList提供了add(E e) ,add(index,E e),addAll(Collection(<? extends E> c),addAll(index,Collection(<? extends E> c)(這倆只能新增泛型)set(index,E e)這五種方法向ArrayList新增元素。

add(E e):

public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
    }
——————————————————
將指定的元素插入此列表中的尾巴
  ensureCapacity方法是擴容操作,elementData[size++] = e;將列表末尾元素指向e

add(index,E e):

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

這個方法中最根本的方法是arraycopy方法 ,該方法的根本目的是將index位置空出來以供新資料的插入,這裡需要進行陣列的右移,這是很麻煩耗時的。多以如果指定的資料集合要進行大量插入的話,建議使用linkedList。

addAll(Collection(<? extends E> c):按照指定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陣列中。

addAll(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;
    }
  • 刪除

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

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

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

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

  • 查詢

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

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

        return (E) elementData[index];
    }
  • 擴容

我們可以看到,在新增方法的原始碼都含有一個函式,ensureCapacity()方法,該方法就是擴容方法,如果新增元素後容量大過初始容量。就會進行擴容操作。所以當我們知道業務資料量或者需要插入大量元素的時候,我們使用ensureCapacity()方法來手動新增初始容量,以減少再擴容的操作

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