Java Web实战篇:增强for循环实现原理及for循环实战性能优化

我是小傅哥 2018-04-17 11:57:33 ⋅ 717 阅读



前言

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


一、增强for循环

1. 三种常用for循环

#普通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 + ","); }

2. 增强for循环实现原理

编译前

for (Integer i : list) {
   System.out.print(i + ",");
}

编译后

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循环底层是通过迭代器模式来实现的。

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

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

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

#代码示例
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() 方法会在删除当前迭代对象的同时维护索引的一致性。

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

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. 嵌套循环

1.1 代码示例

优化前代码示例

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));        

优化后代码示例

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

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

1.2 原理

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

1.3 原理解析
#外小内大
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]++;    }   }  

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

2.1 代码示例

未优化前代码示例

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

优化后代码示例

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

3. 异常捕获

3.1 代码示例

优化前代码示例

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

优化后代码示例

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  
3.2 总结

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

结尾

性能优化的内容有很多,代码优化只是其中一小部分,我们在日常开发中应养成良好的编码习惯。接下来会跟大家探讨更多关于性能优化的内容,希望大家积极交流指导。

关注我们

如果需要源码可以关注“IT实战联盟”公众号并留言(源码名称+邮箱),小萌看到后会联系作者发送到邮箱,也可以加入交流群和作者互撩哦~~~


全部评论: 0

    我有话说:

    移动H5前端五大性能优化方案(实战

    移动H5前端五大性能优化方案(实战

    Java Web实战-代码之美

    代码之美-小小的优化让你的代码Bug更少,执行效率更高

    Java Web实战-轻松提高千万级数据库查询效率

    通过优化数据库设计、java后台和数据库优化达到提高千万级数据查询的效率。

    Java Web实战:发布和运维必备的12条Linux命令

    作为一名Java起步的从业人员,学会一些常用的Linux命令是必须的。

    iOS TableView性能优化

    TableView的性能优化非常考验开发的基本功,之前做项目实战的时候经常被这个问题困扰

    JAVA实现附近范围内公交定位问题

    接上【前端实战:通过JS抓取城市所有站点与线路】获取附近定位信息

    抖音品质建设 - iOS启动优化实战

    前言 启动是 App 给用户的第一印象,启动越慢,用户流失的概率就越高,良好的启动速度是用户体验不可缺少的一环。启动优化涉及到的知识点非常多,面也很广,一文章难以包含全部,所以拆分成两部分:原理

    Netty单机百万连接高性能优化

    关于netty的学习和介绍,可以去github看官方文档,这里良心推荐《netty实战》和《netty权威指南》两本书,前者对于新手更友好,原理和应用都有讲到,多读读会发现很多高性能的优化点。

    基于 GraalVM 的 PHP JIT 实现性能优于原生方案

    GraalVM 是 Oracle 打造的高性能跨语言虚拟机,支持运行 JavaScript、Python 3、Ruby、R、基于 JVM 的语言(如 Java、Scala 和 Kotlin),以及

    Java Web架构实战:聊一聊前后端分离架构

    RESTful思想和Json数据标准的出现,使得这种交互日益便利。Vue.js 用于构建用户界面的渐进式框架

    字节跳动 Go RPC 框架 KiteX 性能优化实践

    本文选自“字节跳动基础架构实践”系列文章。“字节跳动基础架构实践”系列文章是由字节跳动基础架构部门各技术团队专家倾力打造的技术干货内容,和大家分享团队在基础架构发展和演进过程中的实践经验与教训,与

    iOS性能优化实践:头条抖音如何实现OOM崩溃率下降50%+

    iOS OOM 崩溃在生产环境中的归因一直是困扰业界已久的疑难问题,字节跳动旗下的头条、抖音等产品也面临同样的问题。在字节跳动性能与稳定性保障团队的研发实践中,我们自研了一款基于内存快照技术并且可

    京东阅读(web)体验优化实践

    原文:https://www.cnblogs.com/cloud-/p/13366425.html

    抖音品质建设 - iOS启动优化原理

    前言 启动是 App 给用户的第一印象,启动越慢用户流失的概率就越高,良好的启动速度是用户体验不可缺少的一环。启动优化涉及到的知识点非常多面也很广,一文章难以包含全部,所以拆分成两部分:原理实践

    Java 实战-JDK9新特性体验

    JDK9 已经出来好几个月了,我们一起来了解一下JDK9的一些新特性吧

    MySql实战:写一个简单的存储过程,完成订单定时任务

    前言之前我们分享了MySql的性能优化、索引详解等内容,本文章主要是针对想要入门MySql存储过程的读者,主要实现的业务是订单库里面的超过30分钟没有支付的订单全部置为失效订单......

    线性表 - 循环链表

    1.引子 单链表解决了从A 查找到E的过程,假设现在要求从E 查找到A,用时最短, 因为单链表是单向的,只能从前往后,无法解决这个问题。因此引出了循环链表。   思路图

    精品推荐:Java核心数据结构(List,Map,Set)使用技巧与优化

    JDK提供了一组主要的数据结构实现,如List、Map、Set等常用数据结构。这些数据都继承自 java.util.Collection 接口,并位于 java.util 包内。

    iOS实战:iOS 界面卡顿原因

    界面卡顿的原因在 VSync[1] 信号到来后,系统图形服务会通过 CADisplayLink 等机制通知 App,App 主线程开始在 CPU 中计算显示内容......