Java for循环是OutOfMemoryError的原因吗?(月食)

Java for循环是OutOfMemoryError的原因吗?(月食),java,eclipse,memory-leaks,out-of-memory,Java,Eclipse,Memory Leaks,Out Of Memory,您好,我正在编写一个程序,将字符串解析为单个组件,但当我尝试测试它时,会出现内存不足错误。我觉得我的for/while循环是无限的,但我似乎找不到原因 //for loop to loop through char of string for(int i=0; i<expressionString.length(); i++) { //cast char into ascii int int ascii = (int) charAt(i);

您好,我正在编写一个程序,将字符串解析为单个组件,但当我尝试测试它时,会出现内存不足错误。我觉得我的for/while循环是无限的,但我似乎找不到原因

    //for loop to loop through char of string
    for(int i=0; i<expressionString.length(); i++) {

        //cast char into ascii int
        int ascii = (int) charAt(i);

        //appending to token if one of  singly operator symbols: *,/,(,),[,]
        if(ascii == 40 || ascii == 41 || ascii == 42 || ascii == 47 || ascii == 91 || ascii == 93){
            token.append((char) ascii);
            tokenList.add(token.toString());

        } //append if +, -
        else if(ascii == 43 || ascii == 45) {
            token.append((char) ascii);

            //check next char if + or /, if so append to token again
            int nextChar = (char) charAt(i+1);
            if(nextChar == 43 || nextChar == 45) {
                token.append((char) nextChar);
            }
            tokenList.add(token.toString());

        } //appending to token if it's a num
        else if ( ascii >= 48 || ascii <=57) {
            token.append((char) ascii);

            //check if next char is a num
            while ((int) charAt(i+1) >= 48 || (int) charAt(i+1) <= 57) {
                //increment i in for loop to check
                i++;
                token.append((int) charAt(i));
            }
            tokenList.add(token.toString());
        }
        //  
    }
//用于通过字符串的字符进行循环

对于(inti=0;i,这里是您在该循环中所做工作的简化版本

public class Main {

    public static void main(String[] args) {
        String str = "ABCDE";

        StringBuilder sb = new StringBuilder();
        List<String> list = new ArrayList<>();
        for (char c : str.toCharArray()) {
            sb.append(c);                     
            list.add(sb.toString());  // <-- Problem! This adds the *entire* contents of the StringBuilder as a new String to the list.
        }

        System.out.println(list);
    }
}
这是因为每次我们将
char
附加到
StringBuilder
,然后将
StringBuilder
全部内容作为新的
String
添加到
ArrayList

现在假设我们用长度为
1000000
字符串替换
“ABCDE”
,例如,我们将第一行更改为

String str = Stream.generate(() -> "A").limit(1000000).collect(Collectors.joining()); // String of length 1000000
我们现在正在尝试创建1000000个
字符串
对象,其长度从
1
1000000
,结果可以预测

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:3664)
    at java.lang.String.<init>(String.java:207)
    at java.lang.StringBuilder.toString(StringBuilder.java:407)
    at my_package.Main.main(Main.java:17)
线程“main”java.lang.OutOfMemoryError中的异常:java堆空间 位于java.util.Arrays.copyOfRange(Arrays.java:3664) 位于java.lang.String。(String.java:207) 位于java.lang.StringBuilder.toString(StringBuilder.java:407) 在my_package.Main.Main(Main.java:17)中

如何修复它?这取决于您正在尝试做什么(我们没有所有的上下文),但我怀疑您不需要一个
StringBuilder
和一个
列表

这里是您在该循环中所做工作的简化版本

public class Main {

    public static void main(String[] args) {
        String str = "ABCDE";

        StringBuilder sb = new StringBuilder();
        List<String> list = new ArrayList<>();
        for (char c : str.toCharArray()) {
            sb.append(c);                     
            list.add(sb.toString());  // <-- Problem! This adds the *entire* contents of the StringBuilder as a new String to the list.
        }

        System.out.println(list);
    }
}
这是因为每次我们将
char
附加到
StringBuilder
,然后将
StringBuilder
全部内容作为新的
String
添加到
ArrayList

现在假设我们用长度为
1000000
字符串替换
“ABCDE”
,例如,我们将第一行更改为

String str = Stream.generate(() -> "A").limit(1000000).collect(Collectors.joining()); // String of length 1000000
我们现在正在尝试创建1000000个
字符串
对象,其长度从
1
1000000
,结果可以预测

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOfRange(Arrays.java:3664)
    at java.lang.String.<init>(String.java:207)
    at java.lang.StringBuilder.toString(StringBuilder.java:407)
    at my_package.Main.main(Main.java:17)
线程“main”java.lang.OutOfMemoryError中的异常:java堆空间 位于java.util.Arrays.copyOfRange(Arrays.java:3664) 位于java.lang.String。(String.java:207) 位于java.lang.StringBuilder.toString(StringBuilder.java:407) 在my_package.Main.Main(Main.java:17)中

如何修复它?这取决于您正在尝试做什么(我们没有所有的上下文),但我怀疑您不需要一个
StringBuilder
和一个
列表,正如我在评论中所指出的,您在
StringBuilder
中添加任何内容都是可疑的

StringBuilder
只是一个围绕
char[]
的包装器,它会在必要时自动调整大小,以适应您尝试附加的新文本。您可以在堆栈跟踪中看到,在这些自动调整大小的过程中,OOM正在发生

这个问题的一个解决方案是,首先分配一个足够大的缓冲区,然后在
StringBuilder
中添加更多文本之前,不需要进行大小调整:

StringBuilder token = new StringBuilder(MAXIMUM_EXPECTED_SIZE);
这样做的问题是,可能很难确定
最大预期大小
;此外,在大多数情况下,您可能会浪费大量内存,而在缓冲区中添加的文本量与预期大小相差甚远

在将文本传输到
tokenList
后,您似乎实际上不想将其保留在
token
中。您可以使用以下方法将其从缓冲区中明确删除:

token.delete(0, token.length());
// or
token.setLength(0);
(实际上,这并不删除数据,它只允许后续的附件覆盖数据)

但这仍然是浪费:您根本不需要
StringBuilder

考虑一下如何处理这些数字:

     if ( ascii >= 48 || ascii <=57) {
        token.append((char) ascii);

        //check if next char is a num
        while ((int) charAt(i+1) >= 48 && (int) charAt(i+1) <= 57) {
                                   //  ^^ NB
            //increment i in for loop to check
            i++;
            token.append((int) charAt(i));
        }
        tokenList.add(token.toString());
    }

您也可以对其他附加令牌执行类似操作。这只是删除了“中间人”对于
StringBuilder
,这显然避免了重新分配其内部缓冲区的问题。

正如我在评论中指出的,您添加到
StringBuilder
而从未从中删除任何内容的事实是可疑的

StringBuilder
只是一个围绕
char[]
的包装器,它会在必要时自动调整大小,以适应您尝试附加的新文本。您可以在堆栈跟踪中看到,在这些自动调整大小的过程中,OOM正在发生

这个问题的一个解决方案是,首先分配一个足够大的缓冲区,然后在
StringBuilder
中添加更多文本之前,不需要进行大小调整:

StringBuilder token = new StringBuilder(MAXIMUM_EXPECTED_SIZE);
这样做的问题是,可能很难确定
最大预期大小
;此外,在大多数情况下,您可能会浪费大量内存,而在缓冲区中添加的文本量与预期大小相差甚远

在将文本传输到
tokenList
后,您似乎实际上不想将其保留在
token
中。您可以使用以下方法将其从缓冲区中明确删除:

token.delete(0, token.length());
// or
token.setLength(0);
(实际上,这并不删除数据,它只允许后续的附件覆盖数据)

但这仍然是浪费:您根本不需要
StringBuilder

考虑一下如何处理这些数字:

     if ( ascii >= 48 || ascii <=57) {
        token.append((char) ascii);

        //check if next char is a num
        while ((int) charAt(i+1) >= 48 && (int) charAt(i+1) <= 57) {
                                   //  ^^ NB
            //increment i in for loop to check
            i++;
            token.append((int) charAt(i));
        }
        tokenList.add(token.toString());
    }

您也可以对其他附加令牌执行类似操作。这只是删除了“中间人”这显然避免了重新分配内部缓冲区的问题。

第51行是什么?这似乎是发生异常的地方。另外:我认为这里需要一个
&
,而不是
|
(ascii>=48 | | ascii除外:您不需要对“
*,/,(,,[,]”进行注释如果您只是在条件中使用了
ascii=='*'
etc(或
“*/()[]”。indexOf((char)ascii)>=0
)。如果@MFisherKDX的提示不能帮助提供整个类,请。您是forev