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

时间:2017-10-22 17:04:42

标签: java eclipse memory-leaks out-of-memory

您好我正在编写一个将String解析为单个组件的程序,但是当我尝试测试它时,我得到一个内存不足错误。我觉得好像我的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());
        }
        //  
    }

Error

如果这是我的代码错误,请告诉我,因为我似乎无法找到问题所在。谢谢!

2 个答案:

答案 0 :(得分:1)

以下是您在该循环中所做的简化版本。

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);
    }
}

此程序打印

[A, AB, ABC, ABCD, ABCDE]

这是因为每次我们将char附加到StringBuilder时,我们会将StringBuilder整个内容添加为新String转到ArrayList

现在假设我们将"ABCDE"替换为长度为String的{​​{1}},例如我们将第一行更改为

1000000

我们现在正尝试创建长度从String str = Stream.generate(() -> "A").limit(1000000).collect(Collectors.joining()); // String of length 1000000 String的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) StringBuilder

答案 1 :(得分:1)

正如我在评论中指出的那样,你在追加StringBuilder而没有删除任何内容的事实是可疑的。

StringBuilder只是char[]的一个包装器,它会在必要时自动调整大小以容纳您尝试追加的新文本。您可以在堆栈跟踪中看到在其中一个自动调整大小期间发生了OOM。

这个问题的一个解决方案就是最初分配一个足够大的缓冲区,然后调整大小不需要发生,直到StringBuilder附加了更多文本:

StringBuilder token = new StringBuilder(MAXIMUM_EXPECTED_SIZE);

问题在于可能很难确定MAXIMUM_EXPECTED_SIZE;此外,你可能在大部分时间都在浪费大量的内存,而你在缓冲区附近的文本数量附近也是如此。

在您将文字转移到token后,您似乎并不想将文字保留在tokenList中。您可以使用以下命令从缓冲区中明确删除它:

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());
    }

你在这里尝试做的是在i个字符(包括)和j个字符(不包括)之间追加所有内容,其中{{1} }指向字符串的结尾,或指向非数字字符。所以你可以这样做:

j

您可以为其他附加标记执行类似操作。这只是切断了'中间人&#34; if ( ascii >= 48 || ascii <=57) { int j = i + 1; //check if next char is a num while (j < expressionString.length() && charAt(j) >= '0' && charAt(j) <= '9') { j++; } tokenList.add(expressionString.subString(i, j)); i = j; } ,显然避免了重新分配内部缓冲区的问题。