集合应用---集合遍历该如何选择

相信大家在工作中使用集合已经算是家常便饭了吧,而对集合进行遍历也算是必不可少的操作了。而对集合进行遍历也有多种方法,而常用的一般就是for循环和增强for循环(也叫foreach循环)。可能有些人有些迷惑,这两种循环有什么区别呢?我们该如何选择使用呢?用好了系统的性能也能得到一点点的优化哦,接下来就解开神秘的面纱。
先来看一个需求:统计一个省的各科高考平均值,比如数学平均分数。

public static void main(String[] args) {
        //学生数量 80万
        int stuNum = 80*10000;
        //List集合,记录所有学生的分数
        List scores = new ArrayList(stuNum);
        //写入分数
        for (int i = 0; i < stuNum; i++) {
            scores.add(new Random().nextInt(150));
        }
        //记录开始计算时间
        long start = System.currentTimeMillis();
        System.out.println("平均分是:"+average(scores));
        System.out.println("执行时间:"+(System.currentTimeMillis()-start)+"ms");
    }

    private static int average(List list) {
        int sum = 0;
        //foreach遍历求和
        for (int i : list) {
            sum +=i;
        }
        return sum/list.size();
    }


平均分:74
执行时间:16ms

如果我们换成第二种:

private static int average(List<Integer> list) {
        int sum = 0;
        //for循环遍历求和
        for (int i = 0; i < list.size(); i++) {
            sum += list.get(i);
        }
        return sum/list.size(); 
    }
平均分:74
执行时间:0ms

由于第二种执行速度较快在毫秒以内所以显示0ms,可以看到性能提升了不少,当数据量更大时效果会更明显。那为什么第二种遍历会有这么高的性能提升呢?

这是因为ArrayList数组实现了RandomAccess接口(随机存取接口),这也就标志着ArrayList是一个可以随机存取的列表。在java中,RandomAccess和Cloneable、Serializable一样,都是标识性接口,不需要任何实现,只是用来表明其实现类具有某种特质的,实现了Cloneable表名可以被拷贝,实现了Serializable接口表明被序列化了,实现了RandomAccess接口表明这个类可以随机存取,对我们的ArrayList来说也就标志着其数据元素之间没有关联,即两个位置相邻的元素之间没有互相依赖和索引关系,可以随机访问和存储。所以我们说ArrayList是无序的集合。
我们知道,java中的foreach语法是iterator(迭代器)的变形用法,也就是说上面的foreach与下面的代码等价:

for(Iterator i = list.iterator(); i.hasNext();){
    sum += i.next();
}

迭代器是23个设计模式中的一种,”提供一种方法访问一个容器对象中的各个元素,同时又无须暴露该对象的内部细节”,也就是说对于ArrayList,需要先创建一个迭代器容器,然后屏蔽内部遍历细节,对外提供hasNext、nest等方法。而ArrayList实现了RandomAccess接口,已表明元素之间本来没有关系,可是,为了使用迭代器就需要强制建立一种互相“知晓”的关系,比如上一个元素可以判断是否有下一个元素,以及下一个元素是什么等关系,这也就是通过foreach遍历耗时的原因。

java为ArrayList类加上了RandomAccess接口,就是在告诉我们,“嘿,ArrayList是随机存取的”采用下标方式遍历列表速度会更快”,那为什么不把RandomAccess加到所有的List实现类上呢?

那是因为有些List实现类不是随机存取的,而是有序存取的,比如LinkedList类,LinkedList也是一个列表,但它实现了双向链表,每个数据节点中都有三个数据项:前节点的应用(Previous Node)、本节点元素(Node Element)、后继节点的引用(Next Node),也就是说在LinkedList中的两个元素本来就是有关联,所以对于LinkedList来说,使用foreach遍历是不是效率更高呢?

public static void main(String[] args) {
        //学生数量 80万
        int stuNum = 80*10000;
        //List集合,记录所有学生的分数
        List scores = new LinkedList();
        //写入分数
        for (int i = 0; i < stuNum; i++) {
            scores.add(new Random().nextInt(150));
        }
        //记录开始计算时间
        long start = System.currentTimeMillis();
        System.out.println("平均分是:"+average(scores));
        System.out.println("执行时间:"+(System.currentTimeMillis()-start)+"ms");
    }

    private static int average(List list) {
        int sum = 0;
        //foreach遍历求和
        for (int i : list) {
            sum +=i;
        }
        return sum/list.size();
    }

运行结果也是0ms,效率也非常高。我们可以将average方法重构下,以便实现不同的列表采用不同的遍历方式,代码如下:

private static int average(List<Integer> list) {
        int sum = 0;
        if(list instanceof RandomAccess){
            for (int i = 0; i < list.size(); i++) {
            sum += list.get(i);
            }
        } else {
            for (int i : list) {
                sum +=i;
            }
        }
        return sum/list.size();
    }
总结:由上可知,实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器

你可能感兴趣的:(java,集合,集合/遍历,RandomAccess,集合面试)