增强for循环实现原理及for循环实战性能优化

前言

循环就是让我们的程序重复地执行某些业务。在程序设计时,需要处理大量的重复动作,采用循环结构可以降低程序书写的长度和复杂度,可使复杂问题简单化,提高程序的可读性和执行速度。其中,for循环就是循环结构的一种,另外还有while循环和do-while循环语句。但是for循环是开发者最常用的开发方式。

一、增强for循环

三种常用for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
//普通for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ",");
}
//迭代器循环遍历
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + ",");
}
//增强for循环
for (Integer i : list) {
System.out.print(i + ",");
}

增强for循环实现原理

1
2
3
4
//编译前
for (Integer i : list) {
System.out.print(i + ",");
}
1
2
3
4
5
//编译后
Integer i;
for(Iterator iterator = list.iterator();iterator.hasNext(); System.out.println(i)) {
i = (Integer)iterator.next();
}

源码解析

Integer i; 定义一个临时变量i

Iterator iterator = list.iterator(); 获取List的迭代器

iterator.hasNext(); 判断迭代器中是否有未遍历过的元素

i = (Integer)iterator.next(); 获取第一个未遍历的元素,赋值给临时变量i

System.out.println(i) 输出临时变量i的值

通过反编译源码,我们看到,其实JAVA中的增强for循环底层是通过迭代器模式来实现的。

注意:增强for循环可能遇到的坑

既然增强for循环通过迭代器实现,那么必然有迭代器的特性。

Java中有fail-fast机制。在使用迭代器遍历元素的时候,在对集合进行删除的时候一定要注意,使用不当有可能发生ConcurrentModificationException,这是一种运行时异常,编译期并不会发生。只有在程序真正运行时才会爆发。

代码示例

1
2
3
4
for (UserInfo user : userInfos) { 
if (user.getId() == 2)
userInfos.remove(user);
}

会抛出ConcurrentModificationException异常。

Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出
java.util.ConcurrentModificationException异常。
所以 Iterator 在执行的时候是不允许被迭代的对象被改变的。

但你可以使用 Iterator 本身的方法 remove() 来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

正确的在遍历的同时删除元素的示例:

1
2
3
4
5
6
Iterator<UserInfo> userIterator = users.iterator();
while (userIterator.hasNext()) {
UserInfo userInfo = userIterator.next();
if (userInfo.getId() == 2)
userIterator.remove();//这里要使用Iterator的remove方法移除当前对象,如果使用List的remove方法,则同样会出现ConcurrentModificationException
}

二、for循环实战性能优化

循环结构让我们操作数组、集合和其他一些有规律的事物变得更加的方便,但是如果我们在实际开发当中运用不合理,可能会给程序的性能带来很大的影响。所以我们还是需要掌握一些技巧来优化我们的代码的。

嵌套循环

代码示例

优化前代码示例

1
2
3
4
5
6
7
Long stratTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
for (int j = 0; j < 10; j++) {
}
}
Long endTime = System.nanoTime();
System.out.println("外大内小耗时:"+ (endTime - stratTime));

优化后代码示例

1
2
3
4
5
6
7
Long stratTime = System.nanoTime(); 
for (int i = 0; i <10 ; i++) {
for (int j = 0; j < 10000; j++) {
}
}
Long endTime = System.nanoTime();
System.out.println("外小内大耗时:"+(endTime - stratTime));

运行结果:

外大内小耗时:1957590外小内大耗时:1228223
由运行结果来看采用外大内小的方式性能差距还是比较大的。

原理

如果遇到分支结构,就可以利用分支目标缓冲器预测并读取指令的目标地址。分支目标缓冲器在程序运行时将动态记录和调整转移指令的目标地址,可以记录多个地址,对其进行表格化管理。当发生转移时,如果分支目标缓冲器中有记录,下一条指令在取指令阶段就会将其作为目标地址。如果记录地址等于实际目标地址,则并行成功;如果记录地址不等于实际目标地址,则流水线被冲洗。同一个分支,多次预测失败,则更新记录的目标地址。因此,分支预测属于“经验主义”或“机会主义”,会存在一定误测。
————摘抄来源<> 4.4.2 分支优化规则

原理解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//外小内大
for (int i = 0; i <10 ; i++) {
//下面每次循环会预测成功9999次
//第1次没有预测,最后退出循环时预测失败1次
//这样的过程重复10次
for (int j = 0; j < 10000; j++) {
a[i][j]++;
}
}
//外大内小
for (int i = 0; i < 10000; i++) {
//下面每次循环会预测成功9次
//第1次没有预测,最后退出循环时预测失败1次
//这样的过程重复10000次
for (int j = 0; j < 10; j++) {
a[i][j]++;
}
}

消除循环终止判断时的方法调用

代码示例

未优化前代码示例

1
2
3
4
5
Long stratTime = System.nanoTime(); 
for (int i = 0; i < list.size(); i++) {
}
Long endTime = System.nanoTime();
System.out.println("未优化list耗时:"+(endTime - stratTime));

优化后代码示例

1
2
3
4
5
6
Long stratTime = System.nanoTime(); 
int size = list.size();
for (int i = 0; i < size; i++) {
}
Long endTime = System.nanoTime();
System.out.println("优化list耗时:"+(endTime - stratTime));

运行结果

未优化list耗时:27375 优化list耗时:2444

原理

list.size()每次循环都会被执行一次,这无疑会影响程序的性能,所以应该将其放到循环外面,用一个变量来代替,优化前后的对比也很明显。

异常捕获

代码示例

优化前代码示例

1
2
3
4
5
6
7
8
Long stratTime = System.nanoTime();
for (int i = 0; i < 10000000; i++) {
try {
} catch (Exception e) {
}
}
Long endTime = System.nanoTime();
System.out.println("在内部捕获异常耗时:"+(endTime - stratTime));

优化后代码示例

1
2
3
4
5
6
7
8
Long stratTime = System.nanoTime(); 
try {
for (int i = 0; i < 10000000; i++) {
}
} catch (Exception e) {
}
Long endTime = System.nanoTime();
System.out.println("在外部捕获异常耗时:"+(endTime - stratTime));

运行结果

在内部捕获异常耗时:12150142

在外部捕获异常耗时:1955

总结

捕获异常是很耗资源的,所以不要讲try catch放到循环内部,优化后同样有好几个数量级的提升。

结尾

性能优化的内容有很多,代码优化只是其中一小部分,我们在日常开发中应养成良好的编码习惯。

https://www.toutiao.com/i6545291267696230915/