List的實現類主要是ArrayList和LinkedList,兩個主要的差異是ArrayList是經過數組實現的,但LinkedList是經過鏈表實現。
能夠想象,ArrayList在隨機訪問效率上遠高於LinkedList,由於LinkedList訪問一個元素必須從頭節點開始依次訪問知道找到目標節點,因此時間複雜度爲O(n),而ArrayList的隨機訪問複雜度幾乎是O(1)。
但頻繁進行插入刪除數據的時候,LinkedList的效率遠高於ArrayList,由於LinedList只須要改變指針(引用)的內容,時間複雜度爲O(1),而ArrayList添加與刪除須要大量移動元素,時間複雜度爲O(n)。java
public class ListTest { List<Integer> array = new ArrayList<Integer>(); List<Integer> link = new LinkedList<Integer>(); private final int size; private long start, end; public ListTest(int size) { this.size = size; for (int i = 0; i < size; i++) { int t = (int) (Math.random() * 1000); array.add(t); link.add(t); } } public void ArrayIteratorTraversal() { Iterator<Integer> i = array.iterator(); int t; start = System.currentTimeMillis(); while (i.hasNext()) { t = i.next(); } end = System.currentTimeMillis(); System.out.println("ArrayList使用Iterator遍歷" + size + "個數據花了" + (end - start) + "ms"); } public void LinkIteratorTraversal() { Iterator<Integer> i = link.iterator(); int t; start = System.currentTimeMillis(); while (i.hasNext()) { t = i.next(); } end = System.currentTimeMillis(); System.out.println("LinkedList使用Iterator遍歷" + size + "個數據花了" + (end - start) + "ms"); } public void ArrayListIteratorTraversal() { ListIterator<Integer> i = array.listIterator(); int t; start = System.currentTimeMillis(); while (i.hasNext()) { t = i.next(); } end = System.currentTimeMillis(); System.out.println("ArrayList使用ListIterator遍歷" + size + "個數據花了" + (end - start) + "ms"); } public void LinkListIteratorTraversal() { ListIterator<Integer> i = link.listIterator(); int t; start = System.currentTimeMillis(); while (i.hasNext()) { t = i.next(); } end = System.currentTimeMillis(); System.out.println("LinkedList使用ListIterator遍歷" + size + "個數據花了" + (end - start) + "ms"); } public void ArrayCycleTraversal() { int t; start = System.currentTimeMillis(); for (int i = 0; i < size; i++) t = array.get(i); end = System.currentTimeMillis(); System.out.println("ArrayList使用循環遍歷" + size + "個數據花了" + (end - start) + "ms"); } public void LinkCycleTraversal() { int t; start = System.currentTimeMillis(); for (int i = 0; i < size; i++) t = link.get(i); end = System.currentTimeMillis(); System.out.println("LinkedList使用循環遍歷" + size + "個數據花了" + (end - start) + "ms"); } public static void main(String[] args) { ListTest t = new ListTest(10000000); System.out.println("*******開始*******"); t.ArrayIteratorTraversal(); t.LinkIteratorTraversal(); t.ArrayListIteratorTraversal(); t.LinkListIteratorTraversal(); t.ArrayCycleTraversal(); // t.LinkCycleTraversal(); System.out.println("*******結束*******"); } }
其中分別用了Iterator,ListIterator和循環來訪問ArrayList和LinkedList,輸出結果爲
//輸出結果
*開始*
ArrayList使用Iterator遍歷100000個數據花了9ms
LinkedList使用Iterator遍歷100000個數據花了9ms
ArrayList使用ListIterator遍歷100000個數據花了8ms
LinkedList使用ListIterator遍歷100000個數據花了9ms
ArrayList使用循環遍歷100000個數據花了6ms
LinkedList使用循環遍歷100000個數據花了8049ms
*結束*
//輸出結果數組
使用迭代器時,ArrayList比LinkedList要快上很多,屢次測試大約是三倍的樣子,Iterator和ListIterator遍歷一樣的容器效率差很少。可是經過循環遍歷,LinkedList跟ArrayList差了三個數量級,其實用循環遍歷,看似是順序遍歷List,可是,經過List.get()的方式過的元素值,實際上是經過隨機訪問來實現的,因此也從這個例子能夠證實上面所說LinkedList的隨機訪問效率是很是差的。
因爲數據再增加,最後一項LinkedList循環遍歷的測試等待時間很是長,因此把這一項測試去掉,你們知道這個效率很是差就行了。咱們把測試數據規模擴大到10billion:
//輸出結果
*開始*
ArrayList使用Iterator遍歷10000000個數據花了37ms
LinkedList使用Iterator遍歷10000000個數據花了121ms
ArrayList使用ListIterator遍歷10000000個數據花了38ms
LinkedList使用ListIterator遍歷10000000個數據花了117ms
ArrayList使用循環遍歷10000000個數據花了30ms
*結束*
//輸出結果
擴大數據規模後發現有一點是跟上一次測試結果相同的,就是ArrayList的效率明顯比LinkedList要高一些,並且另外發現一個問題,用循環遍歷ArrayList比用迭代器也要快一些。這是爲何呢?
一、 爲何ArrayList循環遍歷比迭代器遍歷快?markdown
public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }
這是ArrayList中一個繼承了Iterator的內部類的next()方法,看了源代碼就能夠發現其實ArrayList的Iterator就是直接訪問ArryList中用於存放數據的一個數組,可是多了一些檢查工做。並且,除此以外,遍歷時每次iterator.hasNext()也須要額外的時間開銷,因此這樣遍歷天然會比較滿一些。
二、 爲何用迭代器遍歷ArrayList比LinkedList快一些呢?
這個問題從源碼角度還真很差說,搜了一些資料,發現你們對這個效率緣由答案都不是特別明確,依稀看到有人的解釋我以爲仍是蠻有道理的,就是ArrayList的數據結構內存分配是連續的,而LinkedList是不連續的,訪問下一個元素的時候須要經過地址去查找,因此有了額外的時間開銷。數據結構
有這麼一使用Arrays.asList的例子:dom
List<Integer>list=Arrays.asList(5,6,8,2,45,1,2,8,5,125,6,8,5); list.add(5);
經過Arrays.asList方法把一串數字轉化成list,而後調用list中的add方法添加元素。可是運行會發現,編譯器拋出了一個java.lang.UnsupportedOperationException異常,問題就在add方法,其實除了add方法,remove,retain等對list元素個數會產生變化的方法都是會報這個異常的,究其緣由,看一下Arrays.asList的源碼
乍一看,發現Arrays.asList返回的就是一個ArrayList對象,可是仔細看發現這個ArrayList並不是java.util.ArrayList,而是Arrays的一個內部類,並且沒有實現剛纔說的add那些方法,而是直接繼承了AbstractList,而後AbstractList並無實現add等方法,只是直接拋出了UnsupportedOperationException異常,因此纔會有上述代碼發生的那些狀況。測試
正確的使用:大數據
List<Integer> list = new ArrayList<Integer>(Arrays.asList(5, 6, 8, 2, 45, 1, 2, 8, 5, 125, 6, 8, 5)); list.add(5);
對於LinekdList和ArrayList的差別,主要就差異在隨機訪問與修改,因此建議以下:
一、 若是須要大量刪除與添加元素,使用LinkedList
二、 若是須要大量隨機訪問元素,使用ArrayList
三、 若是須要常常遍歷,二者都可,ArrayList效率略高,可是若是選用LinedList,在遍歷時儘可能使用迭代器,不然至關於隨機存取,效率很是低。this