声明:如下内容都是基于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