不同的列表类采用不同的遍历方式

tech2022-07-16  165

接下来实现一个功能,计算60万个学生的成绩平均分

使用foreach遍历ArrayList的demo:

import java.util.ArrayList; import java.util.List; import java.util.Random; public class Test { public static int average(List<Integer> scores) { int sum = 0; for (Integer score : scores) { sum = sum + score; } return sum / scores.size(); } public static void main(String[] args) { int studentNum = 60 * 10000;//学生数量 List<Integer> scores = new ArrayList<>(studentNum); for (int i = 0; i < studentNum; i++) { scores.add(new Random().nextInt(100)); } long startTime = System.currentTimeMillis(); System.out.println("平均分:" + average(scores)); long endTime = System.currentTimeMillis(); System.out.println("耗时:" + (endTime - startTime) + "ms"); } }

运行结果如下:

使用下标遍历ArrayList的demo:

import java.util.ArrayList; import java.util.List; import java.util.Random; public class Test { public static int average(List<Integer> scores) { int sum = 0; for (int i = 0; i < scores.size(); i++) { sum = sum + scores.get(i); } return sum / scores.size(); } public static void main(String[] args) { int studentNum = 60 * 10000;//学生数量 List<Integer> scores = new ArrayList<>(studentNum); for (int i = 0; i < studentNum; i++) { scores.add(new Random().nextInt(100)); } long startTime = System.currentTimeMillis(); System.out.println("平均分:" + average(scores)); long endTime = System.currentTimeMillis(); System.out.println("耗时:" + (endTime - startTime) + "ms"); } }

运行结果:

可以发现使用下标来遍历ArrayList,性能大概提高了42%左右。

先看看foreach遍历操作是怎样的,直接看下编译后的部分class文件,如下: 可以发现foreach就是个语法糖,其实本质上还是用的迭代器模式,所以多出了需要判断是否存在下一个元素,然后才获取下一个元素的操作,这也是foreach遍历耗时的原因。

那是不是说可以放弃foreach了,都用下标遍历是不是会更好,毕竟好像开发用了这么久的foreach,只是因为使用方便而已?

使用foreach可以说是多了几步耗时的操作,而使用下标遍历则节省了哪些操作,所以下标遍历会更快,但是这是因为ArrayList是随机存取的,前后元素之间是没有什么关联关系的,所以下一个元素是否存在的这种判断就可以省去了,因为反正通过下标就能取到,那有没有前后元素是有关联关系的集合呢,有的,LinkedList就是其中之一,所以接下来使用LinkedList来存取学生分数,不过这里的学生数用一万就好了,60万学生成绩使用下标遍历测试时间就比较久了。

使用下标遍历LinkedList的demo:

import java.util.LinkedList; import java.util.List; import java.util.Random; public class Test { public static int average(List<Integer> scores) { int sum = 0; for (int i = 0; i < scores.size(); i++) { sum = sum + scores.get(i); } return sum / scores.size(); } public static void main(String[] args) { int studentNum = 10000;//学生数量 List<Integer> scores = new LinkedList<>(); for (int i = 0; i < studentNum; i++) { scores.add(new Random().nextInt(100)); } long startTime = System.currentTimeMillis(); System.out.println("平均分:" + average(scores)); long endTime = System.currentTimeMillis(); System.out.println("耗时:" + (endTime - startTime) + "ms"); } }

运行结果如下: 使用foreach遍历LinkedList的demo:

import java.util.LinkedList; import java.util.List; import java.util.Random; public class Test { public static int average(List<Integer> scores) { int sum = 0; for (Integer score : scores) { sum = sum + score; } return sum / scores.size(); } public static void main(String[] args) { int studentNum = 10000;//学生数量 List<Integer> scores = new LinkedList<>(); for (int i = 0; i < studentNum; i++) { scores.add(new Random().nextInt(100)); } long startTime = System.currentTimeMillis(); System.out.println("平均分:" + average(scores)); long endTime = System.currentTimeMillis(); System.out.println("耗时:" + (endTime - startTime) + "ms"); } }

运行结果如下:

可以发现使用foreach遍历LinkedLis后,性能直线提高了96%左右。

可以看下下标遍历LinkedList慢的原因,跟进LinkedList的get(int index)方法中,如下: 从截图中源码可以清晰的看到LinkedList的get(int index)方法的逻辑,先把下标和中间值做比较,如果小于中间值,则从头节点开始遍历,如果大于中间值,则从尾节点开始反向遍历。因此每次调用get(int index)方法时都会遍历一次,所以遍历耗时的原因就在这了。

总结

ArrayList遍历建议用下标,LinkedList遍历建议用迭代器,不同的列表类试着采用不同的遍历方式。

最新回复(0)