【问题标题】:Is the for loop the reason for OutOfMemoryError? (Eclipse) [duplicate]for 循环是 OutOfMemoryError 的原因吗? (日食)[重复]
【发布时间】:2018-04-03 06:20:45
【问题描述】:

您好,我正在编写一个将字符串解析为单个组件的程序,但是当我尝试对其进行测试时,出现内存不足错误。我觉得我的 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());
        }
        //  
    }

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

【问题讨论】:

  • 第 51 行是什么?这似乎是发生异常的地方。另外:我认为您在这里需要&amp;&amp; 而不是||( ascii &gt;= 48 || ascii &lt;=57)
  • 旁白:如果您只是在条件中使用ascii == '*' 等(或"*/()[]".indexOf((char) ascii) &gt;= 0),则无需评论“*,/,(,),[,]”。
  • 如果@MFisherKDX 的提示不能帮助提供全班请。
  • 你永远附加到token 的末尾,永远不会从中删除。您打算这样做,还是应该在tokenList.add 调用后删除其内容?
  • tokenList 是一个数组列表,我正在尝试将每个 token 分别附加到数组列表中。我不确定我是否理解您删除其内容的意思——我为什么需要删除它?如果您可以详细说明,@AndyTurner

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


【解决方案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" 替换为长度为1000000String,例如我们将第一行更改为

String str = Stream.generate(() -> "A").limit(1000000).collect(Collectors.joining()); // String of length 1000000

我们现在正尝试创建 1000000 个String 对象,长度从11000000,结果可预测。

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)

如何解决?这取决于您要做什么(我们没有所有上下文),但我怀疑您不需要StringBuilderList

【讨论】:

    【解决方案2】:

    正如我在 cmets 中指出的那样,您附加到 StringBuilder 却从未从中删除任何内容的事实令人怀疑。

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

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

    StringBuilder token = new StringBuilder(MAXIMUM_EXPECTED_SIZE);
    

    这样做的问题是可能很难确定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());
        }
    

    您显然在这里尝试做的是在i-th 字符(包括)和j-th 字符(不包括)之间附加所有内容,其中j 指向末尾字符串,或非数字字符。所以你可以这样做:

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

    您可以对其他附加标记执行类似操作。这只是切断了StringBuilder 的“中间人”,这显然避免了它重新分配其内部缓冲区的问题。

    【讨论】:

      猜你喜欢
      • 2016-09-17
      • 1970-01-01
      • 2021-12-20
      • 2023-03-07
      • 2020-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-31
      相关资源
      最近更新 更多