接下来实现一个功能,计算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遍历建议用迭代器,不同的列表类试着采用不同的遍历方式。