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、減小了序列化之後檔案的大小。