最近在溫習Java集合部分,花了三天時間讀完了ArrayList與LinkedList以及Vector部分的源碼。以前都是停留在簡單使用ArrayList的API,讀完源碼看完很多文章後總算是對原理方面有了較清楚的認知。這部分文章整理基本都是這麼一個套路:簡單歸納,分析源碼增刪改查,總結成文。java
Java容器源碼(精品):面試
https://blog.csdn.net/panweiwei1994/article/details/76555359#commentBox 數組
http://www.javashuo.com/article/p-uqkohdtn-dg.html安全
ArrayList動態擴容分析:數據結構
https://blog.csdn.net/zymx14/article/details/78324464dom
接下來整理了下關於List集合校招常見的面試題目:post
相同點:性能
都實現了List接口,容許元素重複和爲null值。測試
底層都是數組,咱們能夠按位置索引出某個元素spa
不一樣點:
ArrayList是非同步的,在線程上不安全而Vector集合給它的每一個API都套上了synchronized,因此Vector線程上是安全的。
在添加元素實現擴容的時候,ArrayList集合存儲空間是增加爲原來的1.5倍。而Vecto集合是存儲空間增長爲原來的2倍
PS:其實不考慮線程問題,他兩沒啥區別。
ArrayList的構造方法有三種。當數據量比較大,這裏又已經明確是一萬條了,咱們應該在初始化的時候就給它設置好容量。
否則使用無參構造器初始容量只有10,後面要擴容,擴容又比較傷性能,由於涉及到數組的複製,將原來的數組複製到新的存儲區域中去。
PS:ArrayList動態擴容是一個重點必定要理解好,附上傳送門:https://blog.csdn.net/zymx14/article/details/78324464
作過測試,ArrayList要是一直使用add方法在尾部插入的話,當數據量很是大的時候,其效率是要比LinkedList快的。刪除也是。
可是要是在頭部插入就要比LinkedList效率低很多。
可見測試分析報告:http://www.javashuo.com/article/p-fjbcdnpr-cy.html
Arraylist遍歷性能要好,不管是所有遍歷仍是遍歷某個元素都是這樣。它底層使用數組,適合使用for循環按下標尋址。而LinkedList底層使用雙向鏈表,雖然說它在遍歷的時候會採起折半查找的策略來提高速度但仍是比不上ArrayList的速度。
若是是使用無參構造的話,初始容量是10,當超過了10個容量時,就須要擴容,每次擴容爲以前的1.5倍。調用的是Arrays.copy(elementData,newCapacity)。因此擴容涉及到數組的複製和移動,咱們應該避免擴容。在初始化的時候預估容量大小。
ArrayList底層使用數組,LinkedList底層使用雙向鏈表。結合數據結構的特色可知,數組在空間上佔用一片連續的內存空間,查詢快。鏈表在增刪操做只須要修改鏈表指針結點就可,增刪快。
因此在查詢使用較多時,選擇ArrayList集合,增刪使用較多時,選擇LinkedList集合。
把ArrayList看成參數傳遞到某個方法中去,涉及到一個淺拷貝深拷貝的問題。若是直接賦值給成員變量時,當成員變量發生改變時,其對應的傳遞過來的ArrayList也會改變
通常不直接用「=」賦值,這是將引用指向了同一片地址,一個修改了裏面的內容另外一個也會跟着變更。
通常採用以下方式:
ArrayList A = new ArrayList();
//假設此時A集合已經有了數據。構造器法 ArrayList B = new ArrayList(A);
//clone法
ArrayList A = new ArrayList(); ArrayList B = (ArrayList) A.clone();
ArrayList A = new ArrayList(); //初始化時最好先預估容量大小 ArrayList B = new ArrayList(); B.addAll(A);
還能夠調用Collections.copy()方法,參考博客:https://blog.csdn.net/tiantiandjava/article/details/51072173
查看源代碼能夠發現,當經過索引去增刪元素的時候效率是比較低的,由於要頻繁的進行數組的複製和移動,若是常常增刪的話咱們能夠去考慮其餘的集合。
經過索引增長過程:1.檢查索引是否越界?2.檢查容量是否足夠?3調用System.arrayCopy(......)操做,效率較低
經過索引刪除元素:1.檢查索引是否越界?2.調用System.arrayCopy(....)操做
源代碼截圖以下:
以上是常見的面試題:接下來總結一下本身看源碼所學到的
ArrayList:
1.ArrayList繼承了AbstractList類,實現了List接口,RadomAccess,Clonable,Serialize接口。結合List接口特色可知它是有序的,能夠存儲重複值且能夠爲null。實現了RadomAccess接口在加上它底層採用數組實現,因此遍歷速度快且遍歷方式推薦使用for循環遍歷下標而不用for-each或者iterator迭代器遍歷。
2.使用默認無參構造時,初始容量分配爲10.當進行增操做時,容量不夠會進行擴容操做。
理清ArrayList增刪改查操做:
add(E e):數組末尾進行增。
1.判斷容量是否足夠
2.size++
因而可知在末尾進行增操做時,是偶爾須要進行一次擴容操做,擴容時候會調用Arrays.copyOf(elementData, newCapacity);也與上文所說的在末尾插入效率並不比LinkedList低
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
public void add(int index, E element):按索引增。可知按索引增每次都要進行數組複製,因此效率低
1.判斷索引是否越界 2.判斷容量是否足夠 3.size++
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
public boolean addAll(Collection<? extends E> c):增長一個集合。
1.先將所要增長的集合轉爲數組 2.判斷容量 3:size++
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
public E remove(int index):按索引值刪除。可知每次都要進行數組複製,效率同樣不高
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; }
public boolean remove(Object o):按值刪除 調用了fastRemove(index)方法,其內部也是要進行數組的複製,效率依然不高。
public boolean remove(Object o) { 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; }
LinkedList:
1.繼承了AbstractSequentialList類,但AbstractSequentialList 只支持按次序訪問,而不像 AbstractList 那樣支持隨機訪問。這是LinkedList隨機訪問效率低的緣由之一。
2.實現了List接口,說明有序,能夠存儲重複值,容許元素爲null。實現了Deque接口,說明能夠像操做棧和隊列同樣操做它。另外它仍是能夠被克隆和序列化的。
3.LinkedList底層是雙向鏈表,鏈表在空間上是離散的,儘管Linked遍歷時採用了折半查找,但效率依然較低。它的優點在於增刪操做,尤爲是在頭結點。