网上一直流传这几种循环遍历的比较,说某某这样写比较好,又说某某那样写比较好,你就那么轻易相信别人说好就好了?难道你出去时,别人给你推销说这个东西好,那你就得买?这是一个道理。鉴于这种情况,我一般都会自己去动手试试,我倒是要看看那些人说的到底对不对,这难道不是程序员必备的基本想法吗?反正,我可不想按照别人说的那样,让你怎么做就怎么做。
在这里,我认为那些流传的几种写法在性能在的比较未必是错的,可能别人的测试是在 JDK 很早的版本之前进行的,但这都什么年代了,还在把别人 N 年前测试的数据拿来秀,太真实了。
好了,今天要测试的就是这几种 for 循环以及一种迭代器的性能和总耗时长。首先,我把我测试的数据内容、数据量以及测试的对象和 JDK 版本介绍一下。
测试平台:JDK1.9、数据大小 1000W 条、数据存放在 ArrayList、数据元素:Person 实体类、循环体内执行的操作时间复杂度为 O(1),数据源代码如下:
List<Person> personList = new ArrayList<>();
Person p = null;
for (int i = 0; i < 10000000; i++) {
p = new Person("" + i, 18, 5000);
personList.add(p);
}
1、第一种循环遍历写法,也是我们常常这样干的:
long start = System.nanoTime();
for (int i = 0; i < personList.size(); i++) {
String name = personList.get(i).getName();
int salary = personList.get(i).getSalary();
int age = personList.get(i).getAge();
}
long end = System.nanoTime();
System.out.println("耗时:" + (end - start));
- 耗时记录(单位:纳秒)
- 56 385 677
- 57 089 676
- 55 864 345
2、第二种写法,把 size 提到外面来赋值,这种写法在源码中也很常见
long start = System.nanoTime();
int size = personList.size(); // 比上面加了一个 size 变量
for (int i = 0; i < size; i++) {
String name = personList.get(i).getName();
int salary = personList.get(i).getSalary();
int age = personList.get(i).getAge();
}
long end = System.nanoTime();
System.out.println("耗时:" + (end - start));
- 耗时记录(单位:纳秒)
- 55 618 123
- 56 231 455
- 55 987 012
3、第三种 foreach 的形式,代码量比较少,这是优点;缺点是如果要获取遍历次数,就要定义一个变量来操作
long start = System.nanoTime();
for (Person person : personList) {
String name = person.getName();
int salary = person.getSalary();
int age = person.getAge();
}
long end = System.nanoTime();
System.out.println("耗时:" + (end - start));
- 耗时记录(单位:纳秒)
- 58 332 341
- 56 330 566
- 57 217 231
4、第四种,倒序遍历,有点反常,看不习惯,不过也可能对应某些特殊场景需要。
long start = System.nanoTime();
for (int i = personList.size() - 1; i > -1; i--) {
String name = personList.get(i).getName();
int salary = personList.get(i).getSalary();
int age = personList.get(i).getAge();
}
long end = System.nanoTime();
System.out.println("耗时:" + (end - start));
- 耗时记录(单位:纳秒)
- 54 814 125
- 55 777 234
- 55 624 790
5、第五种,是迭代器的写法,也有人推荐,说性能好。
long start = System.nanoTime();
Iterator<Person> it = personList.iterator();
while (it.hasNext()) {
Person person = it.next();
String name = person.getName();
int salary = person.getSalary();
int age = person.getAge();
}
long end = System.nanoTime();
System.out.println("耗时:" + (end - start));
- 耗时记录(单位:纳秒)
- 57 920 341
- 57 161 232
- 58 355 896
其实,从这几种测试得出的结果来看,它们之间的差距可以说是微乎其微,可以说在性能方面,几乎是一个样的。上面的时间单位是纳秒,来算一下,50 000 000纳秒相当于 0.05 秒
而从上面的结果差距来看,他们之间的差距也就是在 0.05秒 / 10,0.005秒的差距,这个几乎可以忽略不计了吧。不是否认这些代码的贡献,这可能是前辈们在 JDK 很早之前测试的情况,那时候我估计还在上小学呢,这是一个上面概念。
在 JDK1.9 的版本里,这几种循环遍历的性能几乎一样,可以说是一样的。不信的话,你自己动手测试一下就知道,看看自己的JDK版本,我没装过旧的版本,所以我只能对 JDK1.9 的版本来证明这几种遍历。
还有就是写循环遍历的时候,每个人都有自己的习惯,难免产生不同的写法也是很正常的。我就比较喜欢用 foreach 的写法,看起来很简练。以上,纯属本人在测试后得到的真实数据,若觉的我有什么地方不对,欢迎指正!
其实,我写这篇文章的目的是,我在偶然刷到了一篇文章,说 Java 的30几个性能优化知识里面提到循环遍历的性能,看了一下那测试结果(时间是18年的),大吃一惊,耗时差了几千倍,而且下面的评论还有理有据的。我就在想 jvm 优化这么差吗,我也怀疑自己之前写的那些循环遍历的代码,然后赶紧自己测试起来,发现在 JDK 版本这么高的今天,还用当年的知识误导别人,自己都没动手测试一下就随便发别人的结果,且不说别人是否是正确的,就算是错误的,转来转去都变成真的了。