1. 程式人生 > >java集合系列01--ArrayList

java集合系列01--ArrayList

以下幾篇文章都是對java集合的一個介紹,這些文章並非都是我的原創,主要是集合了各種途徑獲取的一個總結。對於集合,我們主要從以下四點關注:
1.是否允許為空;
2.是否允許重複資料;
3.是否有序,有序是指讀取順序與存放順序是否一致;
4.是否執行緒安全。

ArrayList

(1)ArrayList 是一個數組佇列,相當於動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些介面。
(2)ArrayList 繼承了AbstractList,實現了List。它是一個數組佇列,提供了相關的新增、刪除、修改、遍歷等功能。
(3)ArrayList 實現了RandmoAccess介面,即提供了隨機訪問功能。RandmoAccess是java中用來被List實現,為List提供快速訪問功能的。在ArrayList中,我們即可以通過元素的序號快速獲取元素物件;這就是快速隨機訪問。稍後,我們會比較List的“快速隨機訪問”和“通過Iterator迭代器訪問”的效率。
(4)ArrayList 實現了Cloneable介面,即覆蓋了函式clone(),能被克隆。
(5)ArrayList 實現java.io.Serializable介面,這意味著ArrayList支援序列化,能通過序列化去傳輸。
(6)和Vector不同,ArrayList中的操作不是執行緒安全的!所以,建議在單執行緒中才使用ArrayList,而在多執行緒中可以選擇Vector或者CopyOnWriteArrayList。
ArrayList是一個以陣列形式實現的集合,首先我們來看一下ArrayList的基本元素。

ArrayList與Collection的關係如圖:
這裡寫圖片描述
ArrayList包含了兩個重要的物件:elementData和Size。
(1)elementData是“Object[]型別的陣列”,它儲存了新增到ArrayList中的的元素。實際上,elementData是一個動態陣列,我們可以通過建構函式ArrayList(int initialCapacity)來執行它的初始化容量;如果通過不含引數的建構函式ArrayList()來建立,則它的預設容量是10。
(2)size是動態陣列的實際大小。

新增元素

public class Test {
    public static
void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("00"); list.add("11"); } }

我們來看一下新增操作的底層原始碼實現:

    public boolean add(E e) {
    //這個方法是為了實現擴容的作用
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return
true; }

底層在呼叫add方法的時候只是給elementData的某個位置添加了一個數據而已,下圖為例這裡寫圖片描述
為了方便理解才這樣畫的,elementData中儲存的應該是堆記憶體中元素的引用,而不是實際的元素,這樣畫會讓人誤以為elementData中存放了實際元素,在這裡提醒一下。
*擴容*
陣列擴容的時候我們會先把陣列乘以3,再除2後加1.為什麼這樣呢?
1、如果一次性擴容太大,必然會造成空間的浪費。
2、如果一次擴容不夠,那麼下一次擴容操作會很快發生,這回降低程式的執行效率,要知道擴容還是比較耗費效能的;
所以擴容多少,是jdk開發人員在時間和空間上的一個權衡。最後呼叫的是Arrays的copyOf方法,將原數組裡的內容全部複製到新的數組裡面。

刪除元素

ArrayList支援兩種刪除方式:1.按照下標刪除;2.按照元素刪除,這會刪除與指定元素相同的第一個元素。

//jdk原始碼
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);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }

兩種刪除方式都是呼叫這段程式碼。都是做了兩件事:
1、將指定元素後面位置的所有元素,利用System.arraycopy方法整體向前移動一位。
2、最後一個位置的元素指定外圍null,這樣gc就會去回收它。

插入元素

插入元素用的也是add方法,其方法與刪除操作類似。

ArrayList的遍歷方式

(1)通過迭代器遍歷

//迭代器遍歷
        String value = null;
        Iterator it = list.iterator();
        while(it.hasNext()) {
            value = (String) it.next();
            System.out.println(value);
        }
        System.out.println("--------分割線-----------");
        //隨機訪問,通過索引遍歷
        String value2 = null;
        for(int i = 0; i < list.size(); i++) {
            value = list.get(i);
        }   
        String value3 = null;
        //for迴圈遍歷
        for(String str : list) {
            value3 = str;
        }

那麼我們來比較一下它們的執行效率如何:
執行結果:

iteratorThroughRandomAccess:3 ms
iteratorThroughIterator:8 ms
iteratorThroughFor2:5 ms

由此可見,遍歷ArrayList時,使用隨機訪問(即,通過索引序號訪問)效率最高,而使用迭代器的效率最低!

ArrayList的優缺點

優點:
1、ArrayList底層是以陣列實現,是一種隨機訪問模式,再加上實現了RandomAccess介面,因此查詢也就是get非常快。
2、ArrayList順序新增非常方便。
缺點:
無論是刪除或插入元素(除非是最後一個元素)都涉及到一次元素複製,如果比較多則是一個耗費效能的過程。
因此ArrayList比較適合順序新增、隨機訪問的場景。

ArrayList和Vector的區別

ArrayList是執行緒非安全的,這很明顯,因為ArrayList中所有方法都不是同步的,在併發的情況下一定會出現執行緒安全問題。那麼解決方法有兩種:
1、使用Collections.synchronizedList方法把ArrayList變成一個執行緒安全的List。

List<String> synchronizedList = Collections.synchronizedList(list);
        synchronizedList.add("44");

2、另一個方法就是使用vector,它是ArrayList執行緒安全版本,大部分實現完全一樣,區別在於:
1、vector是執行緒安全的,ArrayList是非執行緒安全;
2、vector可以指定增長因子,如果該增長因子指定了,那麼擴容的時候每次新陣列大小就會在原基礎上加上增長因子;如果不指定那麼就是原陣列*2。

為什麼ArrayList的elementData是用transient修飾

ArrayList中陣列的定義:

private transient Object[] elementData;

ArrayList實現了serializable介面,這意味著ArrayList可以被序列化,用transient修飾elementData意味著我們不希望elementData陣列序列化。這時為什麼?因為序列化ArrayList的時候,ArrayList陣列未必是滿的,比如說陣列大小是10,現在只有3和陣列,那麼是否有必要序列化整個elementData呢?顯然是沒有必要的,因此ArrayList重寫了writeObject方法,每次序列化都呼叫它,先呼叫defaultWriteObject() 方法序列化ArrayList中非transient元素,然後遍歷elementData,只序列化那些有的元素,這樣:
1、加快了序列化的速度;
2、減小了序列化之後檔案的大小。