聲明:如下內容都是基於jdk1.8的
看過ArrayList 源碼的同窗有沒有注意過有這麼一個細節:<font color='blue'>爲何ArrayList實現了RandomAccess這個接口,可是 LinkedList卻沒有實現這個接口?這是一個空接口,裏面沒有任何的方法,有什麼做用呢?</font>
答案: RandomAccess 是一個標誌接口,代表實現這個這個接口的 List 集合是支持快速隨機訪問的。也就是說,實現了這個接口的集合是支持 快速隨機訪問 策略的。而LinkedList是不能實現隨機訪問的。html
ArrayList包含了兩個重要的對象:elementData 和 size。java
// 隨機訪問 List<String> list = new ArrayList<>(); int size = list.size(); for (int i = 0; i < size; i++) { value = list.get(i); }
// 加強for循環 for (String s : list) { value = s; }
// 迭代器遍歷 Iterator<String> iter = list.iterator(); while (iter.hasNext()) { value = iter.next(); }
list.forEach(p -> { p.hashCode(); });
既然有4種遍歷,那咱們看看哪一種遍歷效率下面咱們經過一個實驗來看下這四種循環的耗時吧:
測試代碼數組
/** * @Date: 2020/4/23 * @Description: */ public class ArrayListTest { public static void main(String[] args) { // 數據預熱 /* List<String> testList = createTestList(10); testForEach(testList); testFor(testList); testRandFor(10,testList);*/ List<Integer> integers = Arrays.asList(10, 50, 100,500,1000, 10000, 50000, 100000, 5000000, 10000000,30000000); for (Integer i : integers) { testRand(i); } } private static void testRand(int size) { System.out.println("-----------次數:" + size + "------------"); List<String> list = createTestList(size); // 隨機訪問經過索引值去遍歷。 long time1 = System.nanoTime(); testRandFor(size, list); long time2 = System.nanoTime(); // 加強for循環 testFor(list); long time3 = System.nanoTime(); // 迭代器遍歷 testIterator(list); long time4 = System.nanoTime(); // forEach + lambda testForEach(list); long time5 = System.nanoTime(); System.out.println("隨機訪問\t\t" + (time2 - time1) / 1000 + " ms"); System.out.println("加強for遍歷\t\t" + (time3 - time2) / 1000 + " ms"); System.out.println("迭代器遍歷\t\t" + (time4 - time3) / 1000 + " ms"); System.out.println("forEach遍歷\t\t" + (time5 - time4) / 1000 + " ms"); System.out.println(); } private static void testRandFor(int size, List<String> list) { for (int i = 0; i < size; i++) { list.get(i).hashCode(); } } private static void testFor(List<String> list) { for (String s : list) { s.hashCode(); } } private static void testIterator(List<String> list) { Iterator<String> iter = list.iterator(); while (iter.hasNext()) { iter.next().hashCode(); } } private static void testForEach(List<String> list) { list.forEach(p -> { p.hashCode(); }); } public static List<String> createTestList(int size) { List<String> list = new ArrayList<>(size); for (int i = 0; i < size; i++) { list.add(UUID.randomUUID().toString()); } return list; } }
測試數據結果以下:數據結構
-----------次數:10------------ 隨機訪問 8 ms 加強for遍歷 5 ms 迭代器遍歷 2 ms forEach遍歷 40358 ms -----------次數:50------------ 隨機訪問 4 ms 加強for遍歷 8 ms 迭代器遍歷 7 ms forEach遍歷 5 ms -----------次數:100------------ 隨機訪問 13 ms 加強for遍歷 18 ms 迭代器遍歷 14 ms forEach遍歷 10 ms -----------次數:500------------ 隨機訪問 54 ms 加強for遍歷 28 ms 迭代器遍歷 24 ms forEach遍歷 57 ms -----------次數:1000------------ 隨機訪問 106 ms 加強for遍歷 56 ms 迭代器遍歷 50 ms forEach遍歷 37 ms -----------次數:10000------------ 隨機訪問 1192 ms 加強for遍歷 892 ms 迭代器遍歷 861 ms forEach遍歷 594 ms -----------次數:50000------------ 隨機訪問 3651 ms 加強for遍歷 2908 ms 迭代器遍歷 2563 ms forEach遍歷 2712 ms -----------次數:100000------------ 隨機訪問 10693 ms 加強for遍歷 5273 ms 迭代器遍歷 9294 ms forEach遍歷 3638 ms -----------次數:5000000------------ 隨機訪問 238922 ms 加強for遍歷 29914 ms 迭代器遍歷 30533 ms forEach遍歷 28016 ms -----------次數:10000000------------ 隨機訪問 431047 ms 加強for遍歷 47151 ms 迭代器遍歷 46371 ms forEach遍歷 38943 ms -----------次數:30000000------------ 隨機訪問 1163935 ms 加強for遍歷 137710 ms 迭代器遍歷 139211 ms forEach遍歷 129960 ms
可是從上面咱們會發現一個奇怪的現象,第一次循環的時候<font color=red>forEach遍歷</font>的時間是最長的儘管數據量很是少也會這樣。可是後面的耗時就正常了。若是放開測試裏面的預熱代碼,每次跑出來的耗時也是正常的。dom
若java8的foreach效率如此低下,爲什麼還要推出?難道jdk的開發人員不會優化一下?帶着這個思考,我仔細看了「已往之不諫」的博主最後爲java8 正名的博客,寫的不錯,測試也很充分(說實話,沒有仔細的閱讀)可是結論很明顯。java8勝了。做者爲了證實java8不是吃素的,確實下了很多功夫。最後的最後,做者提到了,「java8的foreach預熱是jvm級別的,須要預熱。」原文連接感興趣的能夠去看下。jvm
雖然有四種遍歷方式,可是可以正確刪除數據的方式只有兩種測試
Iterator<String> iter = list.iterator(); while (iter.hasNext()) { iter.next().hashCode(); iter.remove(); }
for(int i = list.size()-1;i>=0;i--){ list.remove(i); }
下面再演示下錯誤的刪除操做優化
List<String> list = new ArrayList<>(); list.add("1"); list.add("1"); list.add("2"); for(int i=0;i<list.size();i++){ list.remove(i); } System.out.println(String.join(",",list));
結果輸出:1spa
謹慎使用ArrayList中的subList方法code
ArrayList的subList結果不可強轉成ArrayList,不然會拋出ClassCastException 異常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList. 說明:subList 返回的是 ArrayList 的內部類 SubList,並非 ArrayList ,而是 ArrayList 的一個視圖,對於 SubList 子列表的全部操做最終會反映到原列表上。
List<String> list = new ArrayList<>(); list.add("1"); list.add("1"); list.add("2"); ArrayList<String> strings = (ArrayList)list.subList(0, 1); 運行結果: Exception in thread "main" java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList at com.workit.demo.listener.ArrayListTest.main(ArrayListTest.java:29)
List<String> list = new ArrayList<>(); list.add("1"); list.add("1"); list.add("2"); List<String> subList = list.subList(0, 1); // 對原List增長一個值 list.add("10"); subList.add("11"); // 這一行會報 java.util.ConcurrentModificationException