简体   繁体   English

java堆栈溢出错误递归

[英]java stack overflow error recursion

Im making a method that shifts chars in an array both left and right with a parameter that tells how many times to shift. 我正在制作一个方法,使用一个参数来左移和右移数组中的字符,该参数指示要移位多少次。 It has to finish within 20 milliseconds, so i tried recursion. 它必须在20毫秒内完成,所以我尝试了递归。

//Method that switches place in array //在数组中切换位置的方法

public static void bytt(char[] c, int i, int j){
    char temp = c[i];
    c[i] = c[j];
    c[j] = temp;
}

//This method shifts left //这个方法向左移动

public static char rotasjon1(char[] a, int i){
    if(i > 0){
        bytt(a,i,i-1);
        return rotasjon1(a,i-1);
    }
    else
        return ' ';
}

//This method shifts right //这个方法向右移

public static char reverseRotasjon(char[] a, int i){
    if(i < a.length-1){
        bytt(a,i,i+1);
        return reverseRotasjon(a,i+1);
    }
    else
        return ' ';
}

//This method decides to use right shift or left shift depending on the parameter //此方法根据参数决定使用右移或左移

public static void rotasjon(final char[] a, int k){
    if(a.length == 1 || a.length == 0){
        return;
    }
    if(k >= 0){
        for(int i = 0; i< k; i++){
            char temp = a[a.length-1];
            rotasjon1(a,a.length-1);
            a[0] = temp;
        }
    }

    if(k < 0){
        for(int i = k; i< 0; i++) {
            char temp = a[0];
            reverseRotasjon(a, 0);
            a[a.length - 1] = temp;
        }
    }
}

//All these work fine with this array //所有这些都适用于这个数组

char[] d = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'};
    char[] d0 = {'G', 'H', 'I', 'J', 'A', 'B', 'C', 'D', 'E', 'F'};

    Oblig1.rotasjon(d, 4);

d = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'};
    Oblig1.rotasjon(d, -6);

//But i get java.lang.StackOverflowError with this array //但是我得到了带有这个数组的java.lang.StackOverflowError

char[] x = new char[100_000];
    Oblig1.rotasjon(x, 99_999);

I know the array is big an stuff, but is it possible to fix this or do i have to go back to traditional for loops ? 我知道数组很重要,但有可能解决这个问题,还是我必须回到传统的for循环? it have to execute within 20 millisecods 它必须在20毫秒内执行

I know the array is big an stuff, but is it possible to fix this or do i have to go back to traditional for loops ? 我知道数组很重要,但有可能解决这个问题,还是我必须回到传统的for循环?

The exception occurs because the recursion is too deep; 发生异常是因为递归太深; ie it requires too many nested calls. 即它需要太多嵌套调用。

Now in many languages this would not matter. 现在有多种语言,这无关紧要。 For example, with a typical functional language you can recurse as deeply as you want / need. 例如,使用典型的函数语言,您可以根据需要进行深度递归。 But the reason that works is that functional languages (and many other language) implement something known as tail-call optimization, where a recursive call at the end of a method call is optimized (by the compiler) into a jump to the start of the method. 但有效的原因是函数式语言(以及许多其他语言)实现了一种称为尾部调用优化的东西,其中在方法调用结束时的递归调用被优化(由编译器)到跳转到开始的方法。

Reference: What Is Tail Call Optimization? 参考: 什么是尾部调用优化?

But Java doesn't support tail-call optimization. 但Java不支持尾调用优化。 (There are sound but complicated reasons for that.) Instead, each call gets a stack frame on the current thread's stack; (有一些合理但复杂的原因。)相反,每次调用都会在当前线程的堆栈上获得一个堆栈帧; ie N-deep recursion requires N stack frames. 即N深递归需要N个堆栈帧。 The problem is that a Java thread has a fixed amount of stack space. 问题是Java线程具有固定数量的堆栈空间。 (The default is typically 1M bytes or less.) Once created, a thread's stack cannot be expanded. (默认值通常为1M字节或更少。)创建后,无法扩展线程的堆栈。 If an algorithm recurses too deeply, the thread runs out of stack space, and the JVM raises an exception ... as you are observing. 如果算法过于冗长,则线程会耗尽堆栈空间,并且JVM会引发异常...正如您所观察到的那样。

So what is the answer? 所以答案是什么?

  • In general, avoid implementing algorithms in Java that may be deeply recursive: 通常,避免在Java中实现可能深度递归的算法:

    • If the algorithm is recursive, try to convert it to an iterative equivalent; 如果算法是递归的,请尝试将其转换为迭代等价物; eg do the tail-call optimization by hand. 例如,手动进行尾调用优化。

    • If the algorithm is iterative, leave it like that! 如果算法是迭代的,那就这样吧!

  • If you really need deep recursion, you can specify the maxiumum stack size for a thread as a constructor parameter. 如果确实需要深度递归,则可以将线程的maxiumum堆栈大小指定为构造函数参数。 (I'm not sure if there are architectural limits, but you will certainly be limited to the amount of memory available ...) (我不确定是否存在架构限制,但您肯定会受限于可用的内存量......)

if so, mabye you have some advice ? 如果是的话,mabye你有一些建议吗? Remember it have to execute within 20 milliseconds. 记住它必须在20毫秒内执行。

  • If your primary goal is to implement this efficiently, don't use recursion instead of iteration. 如果您的主要目标是有效地实现此目的,请不要使用递归而不是迭代。 In Java - it won't be faster, and there is always a potential risk of stack overflow. 在Java中 - 它不会更快,并且总是存在堆栈溢出的潜在风险。

  • In this case, look at using a temporary array and System.arraycopy . 在这种情况下,请查看使用临时数组和System.arraycopy (If you are rotating by 1, you don't need a temporary array. You can rotate by N in steps of 1 at a time, but that is inefficient.) (如果您旋转1,则不需要临时数组。您可以一次以1为步长旋转N ,但这样效率很低。)

  • In this case, look at implementing it as you would rearrange playing cards by hand ... using just two hands (temporary variables). 在这种情况下,看看实现它,因为您将手动重新排列扑克牌...只使用两只手(临时变量)。 This gives a solution to the "rotate by N " problem without using O(N) extra storage. 这给出了“旋转N ”问题的解决方案,而不使用O(N)额外存储。

Super fast rotation using System.arraycopy as suggested by Paul Boddington: 使用System.arraycopy建议的System.arraycopy超快速旋转:

private static void rotate(char[] array, int distance) {
    if (array == null || array.length == 0)
        return; // nothing to rotate
    final int len = array.length;
    int d = distance % len; // eliminate distance overflow, e.g. for len=10, shift +28 is same as +8
    if (d == 0)
        return; // not rotating
    if (d < 0)
        d += len; // convert left shift to right shift, e.g. for len=10, -2 is same as +8
    if (d < len / 2) { // right shift less than half the array
        char[] temp = new char[d];
        System.arraycopy(array, len - d, temp, 0, d);  // save d values at end
        System.arraycopy(array, 0, array, d, len - d); // shift right by d
        System.arraycopy(temp, 0, array, 0, d);        // add saved value at start
    } else { // right shift more than half the array, so better to use left shift for smaller temp space
        d = len - d; // e.g. for len=10, right by 8 is left by 2
        char[] temp = new char[d];
        System.arraycopy(array, 0, temp, 0, d);        // save d values at start
        System.arraycopy(array, d, array, 0, len - d); // shift left by d
        System.arraycopy(temp, 0, array, len - d, d);  // add saved value at end
    }
}

Test 测试

String s = "ABCDEFGHIJ";
for (int i = -11; i <= 11; i++) {
    char[] array = s.toCharArray();
    long start = System.nanoTime();
    rotate(array, i);
    long end = System.nanoTime();
    System.out.printf("%3d: %s   (%dns)%n", i, new String(array), end-start);
}

char[] x = new char[100_000];
for (int d : new int[] { 0, 1, 50_000, 99_999 }) {
    long start = System.nanoTime();
    rotate(x, d);
    long end = System.nanoTime();
    System.out.printf("%5d: %6dns = %fms%n", d, end-start, (end-start) / 1_000_000d);
}

Output 产量

-11: BCDEFGHIJA   (7128ns)
-10: ABCDEFGHIJ   (285ns)
 -9: JABCDEFGHI   (856ns)
 -8: IJABCDEFGH   (855ns)
 -7: HIJABCDEFG   (855ns)
 -6: GHIJABCDEF   (855ns)
 -5: FGHIJABCDE   (855ns)
 -4: EFGHIJABCD   (855ns)
 -3: DEFGHIJABC   (856ns)
 -2: CDEFGHIJAB   (855ns)
 -1: BCDEFGHIJA   (855ns)
  0: ABCDEFGHIJ   (286ns)
  1: JABCDEFGHI   (855ns)
  2: IJABCDEFGH   (856ns)
  3: HIJABCDEFG   (1710ns)
  4: GHIJABCDEF   (856ns)
  5: FGHIJABCDE   (1141ns)
  6: EFGHIJABCD   (855ns)
  7: DEFGHIJABC   (856ns)
  8: CDEFGHIJAB   (855ns)
  9: BCDEFGHIJA   (571ns)
 10: ABCDEFGHIJ   (285ns)
 11: JABCDEFGHI   (855ns)

    0:    285ns = 0.000285ms
    1:  55885ns = 0.055885ms
50000:  43339ns = 0.043339ms
99999:  56169ns = 0.056169ms

Java不支持尾调用优化,因此您需要不同的算法或for循环。

What you are trying to do is call the function 100 000 times with an input array of size 100 000 to 1. The max size that you are trying to fit into the memory is (100 000 + 1) * (100 000 / 2) * 8 (the size of all chars) + 100 000 * 8 (the size of array references) + 100 000 * 24 (the memory required to keep an array structure) bits. 您要做的是使用大小为100 000到1的输入数组调用该函数100 000次。您尝试装入内存的最大大小为(100 000 + 1)*(100 000/2) * 8(所有字符的大小)+ 100 000 * 8(数组引用的大小)+ 100 000 * 24(保持数组结构所需的内存)位。 This is 5 000 450 000 bytes or ~5GB, hence the StackOverflowError. 这是5 000 450 000字节或~5GB,因此是StackOverflowError。 You can still make it run by tweaking the JVM, but in general it's a very bad idea to use deep recursion for large data structures. 您仍然可以通过调整JVM来使其运行,但一般来说,对大型数据结构使用深度递归是一个非常糟糕的主意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM