关于java的try catch放在循环内外的问题,看过挺多文章的;记得15年看过一篇文章说try catch语句千万不要写在循环语句内,不然会降低循环语句的性能,在很长的一段时间里也是这样做的,其实呢,当时已经用java7了,编译的字节码文件已经对try catch使用异常表优化了,性能上其实两者已经没有多大区别了。
下面我们通过字节码来分析try catch在循环体内外的异同。
先贴示例代码:
public class TestTryCatch {
public void tryCatchOutsideWhile(int n) {
try {
while (n > 0) {
System.out.println(n);
n--;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void tryCatchWithinWhile(int n) {
while (n > 0) {
try {
System.out.println(n);
n--;
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
字节码:
图片的说明:
红框表示while语句块内语句 蓝框 1 表示while的条件语句 黄框表示异常语句块内语句
try catch在循环体内外的异同
相同点:
字节码上的第1 - 7 行字节码本质上一样的;蓝框 1 表示的while条件语句的意思是条件不成立则跳出while循环体。
不同点:
try catch在循环体外时(看tryCatchOutsideWhile字节码):
1. 蓝框 1 的while条件语句条件不成立时,跳出循环体多了一步,先跳转到蓝框 2 再跳出循环体。
2. 黄框的异常语句块在循环体外,如果while语句块内发生异常,则直接退出循环,跳转到对应的异常语句块入口执行异常语句块。
try catch在循环体内时(看tryCatchWithinWhile字节码):
1. 蓝框 1 的while条件语句条件不成立时,直接跳出循环体。
2. 黄框的异常语句块在循环体内,如果while语句块内发生异常,直接跳转到对应的异常语句块入口执行异常语句块;最后执行蓝框 2 的语句goto 0继续执行while剩下的循环。
总结:
根据上面的分析:在跳出循环体时,try catch在循环体内比在体外少跳转一步。从两者性能上说,没什么差别,多跳转一步的性能损耗可以忽略不计。
在要求循环体出异常后继续执行剩下的循环时,使用try catch在循环体内;
在要求循环体出异常后立即退出循环体时,使用try catch在循环体外。
题外话:性能与测试
看了一篇关于try catch放在循环体内外的性能测试文章,因为忽略了对JVM的预热,所以测试结果是不正确的。在对自己代码性能测试时千万记住不能忽略这个前提条件,下面是他的测试代码与测试结果,看上去前提条件是一样的,相同的机器与环境,但忽略了一点,在我们自己的机器上测试时,程序启动时JVM没有预热,程序所需要使用的类与字符串常量的环境没有完全加载与初始化,对于第一个执行的方法method1方法运行时间比较长,而后面的方法由于相应的类与环境都以预热,运行的时间都比较短。
/**
* 示例取自下面的文章:
* https://blog.csdn.net/xinhui88/article/details/8281611
* 用这示例想说明测试时不要忽略对JVM的预热,不然不能正确反映测试结果。
* 对于这一点向原作者说声抱歉!
*
* 测试结果为:
* JDK7:
* method1 total: 9846 【放在循环外】
* method2 total: 1266 【放在循环内】
* method3 total: 1523 【不使用try catch】
*
* JDK6:
* method1 total: 3457 【放在循环外】
* method2 total: 3280 【放在循环内】
* method3 total: 1323 【不使用try catch】
*/
public class Main{
public static void main(String[] args)
{
Main ins = new Main();
int size = 10000000;
ins.method1(size);
ins.method2(size);
ins.method3(size);
}
public void method1(int size)
{
long start = System.currentTimeMillis();
ArrayList<String> al = new ArrayList<String>();
String str = null;
try
{
for (int i = 0; i < size; i++)
{
str = "str" + i;
al.add(str);
}
}
catch (Exception e)
{
}
System.out.println("method1 total: " + (System.currentTimeMillis() - start));
}
public void method2(int size)
{
long start = System.currentTimeMillis();
ArrayList<String> al = new ArrayList<String>();
String str = null;
for (int i = 0; i < size; i++)
{
try
{
str = "str" + i;
al.add(str);
}
catch (Exception e)
{
}
}
System.out.println("method2 total: " + (System.currentTimeMillis() - start));
}
public void method3(int size)
{
long start = System.currentTimeMillis();
ArrayList<String> al = new ArrayList<String>();
String str = null;
for (int i = 0; i < size; i++)
{
str = "str" + i;
al.add(str);
}
System.out.println("method3 total: " + (System.currentTimeMillis() - start));
}
}
下面在我的机器上通过调换方法的执行顺序来说明JVM预热对测试的影响:
method1 total: 6392
method2 total: 1258
method3 total: 1468
---------------------------
method2 total: 6399
method1 total: 1164
method3 total: 1545
---------------------------
method2 total: 6385
method3 total: 1134
method1 total: 1458
---------------------------
method1 total: 6331
method1 total: 1173
method2 total: 1448
method3 total: 1288
从最后一组数据来看,通过增加method1方法进行预热(这里主要是方法使用到的字符串常量的加载)后,后面1、2、3数据比较正常,当然从1、2、3方法上看程序还是有抖动,因为我的win10测试环境开了太多其他程序,对测试结果有影响,这也是测试时要考虑的因素,所以要对测试的不同方法位置也要一样(比如要测试的方法都调到第二个位置),还要进行多次掐头去尾再求平均值。