慎用Java递归调用

来源:互联网 发布:php后台admin 编辑:程序博客网 时间:2024/05/22 09:01
      在java语言中,使用递归调用时,如果过多的调用容易造成java.lang.StackOverflowError即栈溢出和程序执行过慢。这是一个潜在Bug和影响程序执行效率问题,需要谨慎使用。

下面先看造成java.lang.StackOverflowError即栈溢出问题:
[java] view plain copy
  1. public class RecursionTest  
  2. {  
  3.   
  4.     public static void recursion(int totalTimes,int time)  
  5.     {  
  6.         if(totalTimes > 1)  
  7.         {  
  8.             System.out.println("这是第 " +  time + "次调用!");  
  9.             totalTimes--;  
  10.             time++;  
  11.             recursion(totalTimes, time);  
  12.         }  
  13.         else  
  14.         {  
  15.             System.out.println("调用结束,共调用了" + time + "次");  
  16.             return;  
  17.         }  
  18.     }  
  19.       
  20.     public static void main(String[] args)  
  21.     {  
  22.         int totalTimes = 1000000;  
  23.         int time = 1;  
  24.         long startTime = System.currentTimeMillis();  
  25.         System.out.println("嵌套调用起始时间:" + startTime);  
  26.         recursion(totalTimes, time);  
  27.         System.out.println("嵌套调用结束时间:" + System.currentTimeMillis());  
  28.         System.out.println("总耗时:" + (System.currentTimeMillis() - startTime));  
  29.         System.out.println("------------------------------------------------------------");  
  30.     }  
  31.   
  32. }  
修改“totalTimes”,当到达一定值时,报如下错误:

在开发时,要注意避免该问题,特别是递归过多调用时,最好改为for或者whlie来代替
如下cycle()方法:
[java] view plain copy
  1. public static void cycle(int totalTimes, int time)  
  2. {  
  3.     if(totalTimes > 1)  
  4.     {  
  5.         System.out.println("这是第 " +  time + "次调用!");  
  6.     }  
  7.     else  
  8.     {  
  9.         System.out.println("调用结束,共调用了" + time + "次");  
  10.     }  
  11. }  
代替recuresion()方法:
[java] view plain copy
  1. public static void recursion(int totalTimes,int time)  
  2.     {  
  3.         if(totalTimes > 1)  
  4.         {  
  5.             System.out.println("这是第 " +  time + "次调用!");  
  6.             totalTimes--;  
  7.             time++;  
  8.             recursion(totalTimes, time);  
  9.         }  
  10.         else  
  11.         {  
  12.             System.out.println("调用结束,共调用了" + time + "次");  
  13.             return;  
  14.         }  
  15.     }  


[java] view plain copy
  1.   
再看一下对程序执行效率的影响:
仍然使用上面的例子,分别cycle()方法和在main()方法中加入如下代码:
[java] view plain copy
  1. startTime = System.currentTimeMillis();  
  2. System.out.println("循环调用起始时间:" + startTime);  
  3. for (int index = totalTimes; index > 0; index--)  
  4. {  
  5.     cycle(index, time);  
  6.     time++;  
  7. }  
  8. System.out.println("循环调用结束时间:" + System.currentTimeMillis());  
  9. System.out.println("总耗时:" + (System.currentTimeMillis() - startTime));  
  10. System.out.println("--------------------  
整个代码如下:
[java] view plain copy
  1. public class RecursionTest  
  2. {  
  3.   
  4.     public static void recursion(int totalTimes,int time)  
  5.     {  
  6.         if(totalTimes > 1)  
  7.         {  
  8.             System.out.println("这是第 " +  time + "次调用!");  
  9.             totalTimes--;  
  10.             time++;  
  11.             recursion(totalTimes, time);  
  12.         }  
  13.         else  
  14.         {  
  15.             System.out.println("调用结束,共调用了" + time + "次");  
  16.             return;  
  17.         }  
  18.     }  
  19.       
  20.     public static void cycle(int totalTimes, int time)  
  21.     {  
  22.         if(totalTimes > 1)  
  23.         {  
  24.             System.out.println("这是第 " +  time + "次调用!");  
  25.         }  
  26.         else  
  27.         {  
  28.             System.out.println("调用结束,共调用了" + time + "次");  
  29.         }  
  30.     }  
  31.       
  32.     public static void main(String[] args)  
  33.     {  
  34.         int totalTimes = 100000;  
  35.         int time = 1;  
  36.         long startTime = System.currentTimeMillis();  
  37.         System.out.println("嵌套调用起始时间:" + startTime);  
  38.         recursion(totalTimes, time);  
  39.         System.out.println("嵌套调用结束时间:" + System.currentTimeMillis());  
  40.         System.out.println("总耗时:" + (System.currentTimeMillis() - startTime));  
  41.         System.out.println("------------------------------------------------------------");  
  42.           
  43.         startTime = System.currentTimeMillis();  
  44.         System.out.println("循环调用起始时间:" + startTime);  
  45.         for (int index = totalTimes; index > 0; index--)  
  46.         {  
  47.             cycle(index, time);  
  48.             time++;  
  49.         }  
  50.         System.out.println("循环调用结束时间:" + System.currentTimeMillis());  
  51.         System.out.println("总耗时:" + (System.currentTimeMillis() - startTime));  
  52.         System.out.println("------------------------------------------------------------");  
  53.     }  
  54.   
  55. }  

分别测试totalTimes为500,1000,3000对比结果:

500次:



1000次:



3000次:



以下是从网络上摘抄的:
根本原因是这样的,对于每一个线程,都有一个java栈 ,当有一个方法被调用的时候,会产生一些跟这个方法相关的信息,如方法名,参数,中间变量等等,这些叫做栈帧 ,当一个方法执行完毕  这个栈帧才会从栈顶pop掉  你递归的话  会一直向栈里push栈帧  而这个java栈是有一定的长度或深度的,当栈满了,无法再进行push的时候 就出现你上面的异常了,解决办法的话 就不要用递归操作 改用for 而且平时也不建议用递归的,效率太低了 .  

栈溢出了,JVM依然是采用栈式的虚拟机,这个和C和Pascal都是一样的。函数的调用过程都体现在堆栈和退栈上了。你调用构造函数的“层”太多了,以致于把栈区溢出了。  
通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要1K的空间(这个大约相当于在一个C函数内声明了256个int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是1-2MB的。通常递归也不要递归的层次过多,很容易溢出.  
 
对java.lang.StackOverflowError的分析:  
原因:运行一个程序,JVM会开辟一块内存空间去储存程序进行时的某些信息,当程序运行时需要储存的信息超过了分配的空间,就会出现那样的问题.比如死循环,  
解决:首先从程序代码优化方面着手,检查是否有死循环、递归等程序,如果有,修正、优化相关代码。  
0 0
原创粉丝点击