Java 递归方法的间歇性堆栈溢出
我为一个课堂作业写了一个简单的方法,它使用递归(是的,它必须使用递归)来计算分形图案中三角形的数量:Java 递归方法的间歇性堆栈溢出,java,recursion,stack-overflow,Java,Recursion,Stack Overflow,我为一个课堂作业写了一个简单的方法,它使用递归(是的,它必须使用递归)来计算分形图案中三角形的数量: public static BigInteger triangleFract(int layer) { if(layer < 0) { throw new IllegalArgumentException("Input must be >= 0"); } else if(layer == 0) { return new BigInteg
public static BigInteger triangleFract(int layer) {
if(layer < 0) {
throw new IllegalArgumentException("Input must be >= 0");
} else if(layer == 0) {
return new BigInteger("0");
} else if (layer == 1) {
return new BigInteger("1");
} else {
return triangleFract(layer - 1)
.multiply(new BigInteger("3"))
.add(new BigInteger("2"));
}
}
公共静态BigInteger triangleFract(int层){
如果(层<0){
抛出新的IllegalArgumentException(“输入必须大于等于0”);
}else if(层==0){
返回新的BigInteger(“0”);
}else if(层==1){
返回新的BigInteger(“1”);
}否则{
返回三角形分形(层-1)
.multiply(新的大整数(“3”))
.add(新的BigInteger(“2”));
}
}
我一直想做的是理解int层有多大,从而限制用户输入。在一些测试之后,我得到大约6700+的堆栈溢出,这很好
让我不安的是,如果layer有数千个,该方法通常会运行,但它仍然会随机遇到StackOverflowError
例如,我选择将图层限制为4444,它似乎几乎总是能够处理这个问题,但偶尔它似乎还是会溢出
为什么会这样?我能做些什么吗 考虑移动到迭代版本。这就是我所认为的,如果你开发一个递归算法,你必须控制层次深度,或者根本不使用递归。允许递归到那个深度是一种设计风格 尝试以下迭代版本:
public static BigInteger triangleFract(int layer) {
if (layer < 0) {
throw new IllegalArgumentException("Input must be >= 0");
}
if (layer == 0) {
return BigInteger.ZERO;
}
BigInteger result = BigInteger.ONE;
BigInteger two = new BigInteger("2");
BigInteger three = new BigInteger("3");
for (int i = 1; i < layer; i++) {
result = result.multiply(three).add(two);
}
return result;
}
公共静态BigInteger triangleFract(int层){
如果(层<0){
抛出新的IllegalArgumentException(“输入必须大于等于0”);
}
如果(层==0){
返回biginger.ZERO;
}
BigInteger结果=BigInteger.ONE;
BigInteger二=新的BigInteger(“2”);
BigInteger三=新的BigInteger(“3”);
对于(int i=1;i
注:
- 使用
和biginger.ZERO
而不是为这些值创建新实例biginger.ONE
- 删除冗余的
-终止语句后没有else
(例如else
)return
- 重新使用
和新的BigInteger(“2”)
而不是每次迭代都创建新实例新的BigInteger(“3”)
也就是说,可能还有许多其他原因,其行为可能取决于您使用的JVM。实际上您可以做一些事情:增加最大堆栈大小。这是在JVM启动时使用选项
-Xss
完成的,如下所示:
java -Xss40m MainClass
小心不要设置过高的值。如果你必须超过60米-70米,那么建议重新设计你的代码。我无法重现你的“波动”效应。这是非常确定的代码,因此每次都应该得到相同的结果(包括堆栈溢出错误) 你是怎么测试的?您是否为4444测试的每次尝试都运行了一个新的jvm?(或者仅仅是三角形(4444);在循环中调用?) 您的操作系统、java版本等是什么 我这样问是因为我真的不喜欢这样未解决的问题——像这样的事情会在哪里(什么时候)伤害你;)
哦。。。顺便说一句,无论如何,你应该使用BigInteger中的1和0常量(并为2和3创建你自己的常量。这应该为你节省不少内存(是的,我知道,这不是你的问题).适用于那些无法再现这种波动的人。从方法可靠抛出的
堆栈溢出错误开始查找层值。该值越接近实际阈值越好。现在从循环内部调用此方法(在我的机器上maxLayer=11500
):
它将抛出StackOverflowerError
。现在将该值减小一点(看起来5-10%就足够了):
在我的机器上,这不会引发任何错误,并成功跳过11500
。实际上,它在16000
之前工作正常
因此,不管它是什么,它都可能涉及JVM优化。我试着用-XX:+printcomployment
运行一个程序。我看到了JIT在循环时是如何工作的:
我发现它通常打印出小于100(95-98)的内容。这与我手动执行时看到的一致。当我跳过发射器时:
for I in `seq 1 100`; do
java \
Triangle 2>&1| grep Stack; done | wc -l
它总是打印出100。我想问:为什么是BigInteger?你也可以使用原语long。我在没有任何堆栈溢出错误的情况下多次运行了triangleFract(7000)
@BlueBullet这将很快超过long
s的容量(大约从layer=41开始)@A.R.S.好的,我明白了。谢谢你的解释。@user1831889:是的,你可以尝试增加statck大小。如果你有那么多内存或-XSS512M,请将其设置为ega的固定值。他问了一个非常具体的问题:它为什么会波动?是的,他提到它必须使用递归。@Bohemian FYI结果将是2*pow(3,第1层)-1
。因此您可以直接返回(新的BigInteger(“3”)).pow(第1层)。乘法(新的BigInteger(“2”)。减法(BigInteger.1)
,并完全避免循环。代码中有一个错误:结果。乘法(3)。加(2);
将返回一个未分配给任何人的BigInteger
。@SoboLAN你完全正确!BigInteger
是不可变的!我犯了一个新手错误。我现在已经修复了代码。谢谢+1。还有一个使用迭代而不是递归的示例(遍历树):递归和迭代之间总是有一条线,有时递归是简化解
int i = 10500;
while (true) {
System.out.println(i);
triangleFract(i++);
}
117 1 java.lang.String::hashCode (64 bytes)
183 2 java.lang.String::charAt (33 bytes)
189 3 sun.nio.cs.UTF_8$Decoder::decodeArrayLoop (553 bytes)
201 4 java.math.BigInteger::mulAdd (81 bytes)
205 5 java.math.BigInteger::multiplyToLen (219 bytes)
211 6 java.math.BigInteger::addOne (77 bytes)
215 7 java.math.BigInteger::squareToLen (172 bytes)
219 8 java.math.BigInteger::primitiveLeftShift (79 bytes)
224 9 java.math.BigInteger::montReduce (99 bytes)
244 10 sun.security.provider.SHA::implCompress (491 bytes)
280 11 sun.nio.cs.UTF_8$Encoder::encodeArrayLoop (490 bytes)
282 12 java.lang.String::equals (88 bytes) 11400
289 13 java.lang.String::indexOf (151 bytes)
293 14 java.io.UnixFileSystem::normalize (75 bytes)
298 15 java.lang.Object::<init> (1 bytes)
298 16 java.util.jar.Manifest$FastInputStream::readLine (167 bytes)
299 17 java.lang.CharacterDataLatin1::getProperties (11 bytes)
300 18 NormalState::triangleFract (74 bytes)
308 19 java.math.BigInteger::add (178 bytes)
336 20 java.lang.String::lastIndexOf (151 bytes)
337 21 java.lang.Number::<init> (5 bytes)
338 22 java.lang.Character::digit (6 bytes)
340 23 java.lang.Character::digit (168 bytes)
342 24 java.lang.CharacterDataLatin1::digit (85 bytes)
343 25 java.math.BigInteger::trustedStripLeadingZeroInts (37 bytes)
357 26 java.lang.String::substring (83 bytes)
360 27 java.lang.String::lastIndexOf (10 bytes)
360 28 java.lang.String::lastIndexOf (29 bytes)
361 29 java.math.BigInteger::<init> (24 bytes)
361 30 java.lang.Integer::parseInt (269 bytes)
361 31 java.math.BigInteger::<init> (8 bytes)
362 32 java.math.BigInteger::<init> (347 bytes)
404 33 java.math.BigInteger::multiply (72 bytes)
404 34 java.math.BigInteger::add (123 bytes)
for I in `seq 1 100`; do
java ... com.intellij.rt.execution.application.AppMain \
Triangle 2>&1| grep Stack; done | wc -l
for I in `seq 1 100`; do
java \
Triangle 2>&1| grep Stack; done | wc -l