递归低效以及java.lang.StackOverflowError原因

现象 :

递归是我们很经典的一种算法实现,可以很好的描述一个算法的原理!对于算法的描述、表现和代码结构理解上,递归都是不错的选择!

但是本文想说的是java实现一个递归算法的时候尽量不要用递归实现,而是转换成的非递归实现。

最近在实现一个比较复杂算法的时候,尝试了一下,非递归实现相比递归实现速度上能提升1/3。

以下面一个简单的例子来说:

(注:为了描述简单,所以这里只用一个简单的例子。这个例子可以用更简单的数学公式来实现,所以请大家不要过分在意。 重点是想说明递归可能带来的一些问题)

输入参数:N

输出结果: log1+log2+log3+....+logN

两种实现代码如下:

 

Java代码  收藏代码
  1. package test;  
  2.   
  3. public class RecursiveTest {  
  4.     /** 
  5.      * 递归实现 
  6.      *  
  7.      * @param n 
  8.      * @return 
  9.      */  
  10.     public static double recursive(long n) {  
  11.         if (n == 1) {  
  12.             return Math.log(1);  
  13.         } else {  
  14.             return Math.log(n) + recursive(n - 1);  
  15.         }  
  16.     }  
  17.   
  18.     /** 
  19.      * 非递归实现 
  20.      *  
  21.      * @param n 
  22.      * @return 
  23.      */  
  24.     public static double directly(long n) {  
  25.         double result = 0;  
  26.         for (int i = 1; i <= n; i++) {  
  27.             result += Math.log(i);  
  28.         }  
  29.         return result;  
  30.     }  
  31.   
  32.     public static void main(String[] args) {  
  33.         int i = 5000000;  
  34.         long test = System.nanoTime();  
  35.         long start1 = System.nanoTime();  
  36.         double r1 = recursive(i);  
  37.         long end1 = System.nanoTime();  
  38.         long start2 = System.nanoTime();  
  39.         double r2 = directly(i);  
  40.         long end2 = System.nanoTime();  
  41.   
  42.         System.out.println("recursive result:" + r1);  
  43.         System.out.println("recursive time used:" + (end1 - start1));  
  44.         System.out.println("non-recursive result:" + r2);  
  45.         System.out.println("non-recursive time used:" + (end2 - start2));  
  46.     }  
  47. }  
 

得到运行结果如下:


recursive result:7.212475098340103E7
recursive time used:539457109 
non-recursive result:7.212475098340103E7
non-recursive time used:282479757

 

可以看出递归的运行时间是非递归运行时间将近2倍。

(注:以上代码还是在-Xss200m的参数下运行的,如果栈空间不足,直接不能运行)

 

原因简单分析: 

 

上图是java线程栈的结构。java将为每个线程维护一个堆栈,堆栈里将为每个方法保存一个栈帧,栈帧代表了一个方法的运行状态。 也就是我们常说的方法栈。最后一个为当前运行的栈帧。

那么每一次方法调用会涉及:

1.为新调用方法的生成一个栈帧

2.保存当前方法的栈帧状态

3.栈帧上下文切换,切换到最新的方法栈帧。

 

递归实现将导致在栈内存的消耗(往往需要调整Xss参数)和因为创建栈帧和切换的性能开销,最终大大的影响效率!

 

所以,如果你想提升你的算法效率,不要使用递归实现是一个基础原则!

另外,递归是我们用来理解算法的一个方法,当用代码来实现的时候基本都可以转换成非递归的代码实现!

上文转自 http://singleant.iteye.com/blog/988345
网络上还有解释:
对于每一个线程,都有一个java栈 ,当有一个方法被调用的时候,会产生一些跟这个方法相关的信息,如方法名,参数,中间变量等等,这些叫做栈帧 ,当一个方法执行完毕  这个栈帧才会从栈顶pop掉  你递归的话  会一直向栈里push栈帧  而这个java栈是有一定的长度或深度的,当栈满了,无法再进行push的时候 就出现你上面的异常了,解决办法的话 就不要用递归操作 改用for 而且平时也不建议用递归的,效率太低了 .   
栈溢出了,JVM依然是采用栈式的虚拟机,这个和C和Pascal都是一样的。函数的调用过程都体现在堆栈和退栈上了。你调用构造函数的“层”太多了,以致于把栈区溢出了。  
通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要1K的空间(这个大约相当于在一个C函数内声明了256个int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是1-2MB的。通常递归也不要递归的层次过多,很容易溢出.  
 
对java.lang.StackOverflowError的分析:  
原因:运行一个程序,JVM会开辟一块内存空间去储存程序进行时的某些信息,当程序运行时需要储存的信息超过了分配的空间,就会出现那样的问题.比如死循环,  
解决:首先从程序代码优化方面着手,检查是否有死循环、递归等程序,如果有,修正、优化相关代码。  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值