【问题标题】:java heap error when append to stringBuilder附加到stringBuilder时出现java堆错误
【发布时间】:2016-04-19 09:59:25
【问题描述】:

在我的程序中,我想读取一个 PLSQL 文件 并删除以 --
开头的 cmets 我将每条评论都放在它自己的行中,这样我就可以删除该特定行(有时我将代码和 cmets 放在同一行中,这就是我正在做的“\n--”)。
我将我的程序导出到一个 jar 文件,它在我的桌面上运行良好,但在另一台计算机上(读取不同的 PLSQL 文件)即使我尝试它也会给我 Java 堆空间错误

java -Xmx256m -jar myjar.jar

错误:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:58)

Caused by: java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.lang.AbstractStringBuilder.expandCapacity(Unknown Source)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(Unknown Source)
    at java.lang.AbstractStringBuilder.append(Unknown Source)
    at java.lang.StringBuffer.append(Unknown Source)
    at ParserDB.ScriptNoComment(ParserDB.java:142)
    at ParserDB.GetTheName(ParserDB.java:54)
    at Rapport.SearchCcInDB(Rapport.java:189)
    at Rapport.listDB(Rapport.java:77)
    at Rapport.main(Rapport.java:472)
    ... 5 more

我的代码是:

public static String ScriptNoComment(String fileName){
    String result = null ;      
    try{
        FileInputStream fstream = new FileInputStream(fileName);
        DataInputStream in = new DataInputStream(fstream);
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        StringBuffer strOut = new StringBuffer();
        StringBuilder Out = new StringBuilder();
        String strLine;

         while ((strLine = br.readLine()) != null)   {

            if(strLine.contains("--")){
                strLine = strLine.replaceAll("--","\n--");
            }
            strOut.append(strLine+"\n");
        }

        in.close();   
        //delete comment
        String[] lines = strOut.toString().split("\\n");
        for(String s: lines){
            if(s.contains("--")){
                s="";
            }
            Out.append(s+"\n");
        }

        result = Out.toString();
        result = result.toUpperCase();      
        result = result.replaceAll("\"", "");
        result = result.replaceAll("\\r\\n|\\r|\\n", " ");
        result = result.replaceAll("\\s+", " ");

        }catch (Exception e){          
       System.err.println("Error: " + e.getMessage());
      }

    return result ;

}

有没有办法优化我的代码,提前谢谢

编辑
1-) 我使用以下命令检查了另一台计算机中的堆大小:

java -XX:+PrintFlagsFinal -version | findstr /i "HeapSize PermSize ThreadStackSize"

结果是:min:16M,Maxsize:256M 所以我应该在 java -jar :-Xmx512m 而不是 -Xms256m 中录音

2-) 我删除了(只是为了测试)stringbuilder和所有replaceAll,但仍然得到同样的错误,因为我的文件太大了。

所以我所做的是计算我正在阅读的每个文件的行数并尝试(取决于行)例如仅读取前 50 行并将我的方法仅应用于这 50 行

谢谢大家的回答

【问题讨论】:

  • 也许只是给它更多的堆空间? -Xmx2g 例如
  • 更好地使用 Stream 方法。如果你的文本很大,它会消耗大量内存,因为每个操作都会分配一个new String()。如果你在读取文件时遇到了麻烦,那么你在正则表达式的操作上就会遇到更大的麻烦。
  • 这个程序太复杂了。最后,您有一个包含输入的 StringBuffer、一个具有相同内容的字符串数组和一个 StringBuilder,它还包含除 cmets 之外的所有内容。这是内存要求的三倍。相反,您可以逐行读取文件,检查该行是否包含注释(或内容),如果是则省略/缩短它。您也可以在线进行替换,因此结果中不需要它们。这样做,您的内存需求将大大减少。
  • @UweAllner 使用 -Xmx2g 是否有任何损坏,我的意思是这个 java -Xmx2g -jar myjar.jar 可以阻止我的计算机吗?
  • @ammoQ 是的,但有时我将代码和 cmets 放在同一行,这就是我正在做的方式“\n--”

标签: java regex stringbuilder heap-memory


【解决方案1】:

如果您有 java 8,则可以尝试使用此代码在处理行时对其进行内联编辑

public static String scriptNoComment(String fileName) {

  Path filePath = Paths.get(fileName);
  try (Stream<String> stream = Files.lines(filePath)) {

    List<String> linesWithNoComments = new ArrayList<String>();

    stream.forEach(line -> {

      if (line.startsWith("--")) {
        return;
      }

      String currentLine = line;

      int commentStartIndex = line.indexOf("--");
      if (commentStartIndex != -1) {
        currentLine = line.substring(0, commentStartIndex);
      }

      currentLine = currentLine.toUpperCase();
      currentLine = currentLine.replaceAll("\"", "");
      currentLine = currentLine.replaceAll("\\r\\n|\\r|\\n", " ");
      currentLine = currentLine.replaceAll("\\s+", " ").trim();

      if (currentLine.isEmpty()) {
        return;
      }

      linesWithNoComments.add(currentLine);

    });

    return String.join("\n", linesWithNoComments);

  } catch (IOException e) {
    e.printStackTrace(System.out);
    return "";
  }
}

如果 java 8 不是一个选项,那么您可以使用 Apache StringUtils::joinFileUtils::LineIterator 来实现相同的结果。希望这能解决问题。

编辑

按照Nicolas Filotto 的建议,我在处理了一定数量的行之后添加了写入文件(这个数字是完全随机选择的)。我测试了这两种方法,第一种方法因文件大小接近堆大小而失败(字符串中的行连接与 OP 代码有相同的问题)。使用第二种方法,我使用 2GB 文件进行了测试,执行 2 分钟后,我在输入文件旁边找到了 ${fileName}_noComments 文件。

public static int LINES_BATCH = 10000;

private static void scriptNoComment(String fileName) {

  Path filePath = Paths.get(fileName);
  try (Stream<String> stream = Files.lines(filePath); BufferedWriter fileOut = getFileOutWriter(fileName)) {

    List<String> linesWithNoComments = new ArrayList<String>();

    stream.forEach(line -> {

      if (line.startsWith("--")) {
        return;
      }

      String currentLine = line;

      int commentStartIndex = line.indexOf("--");
      if (commentStartIndex != -1) {
        currentLine = line.substring(0, commentStartIndex);
      }

      currentLine = currentLine.toUpperCase();
      currentLine = currentLine.replaceAll("\"", "");
      currentLine = currentLine.replaceAll("\\r\\n|\\r|\\n", " ");
      currentLine = currentLine.replaceAll("\\s+", " ").trim();

      if (currentLine.isEmpty()) {
        return;
      }

      linesWithNoComments.add(currentLine);

      if (linesWithNoComments.size() >= LINES_BATCH) {
        writeCurrentBatchToFile(fileOut, linesWithNoComments);
      }

    });

  } catch (IOException e) {
    e.printStackTrace(System.err);
  }
}

private static BufferedWriter getFileOutWriter(String fileName) {
  BufferedWriter fileOut;
  try {
    fileOut = new BufferedWriter(new FileWriter(fileName + "_noComments", false));
    return fileOut;
  } catch (IOException e) {
    throw new RuntimeException("Error while creating out writer", e);
  }
}

private static void writeCurrentBatchToFile(BufferedWriter fileOut, List<String> linesWithNoComments) {
  try {

    for (String line : linesWithNoComments) {
      fileOut.write(line + " ");
    }

    linesWithNoComments.clear();
  } catch(IOException e) {
    throw new RuntimeException("Unable to write lines to file", e);
  }
}

【讨论】:

  • ArrayList 并不比StringBuilder 好。最好使用具有文件大小初始容量的StringWriter
【解决方案2】:

假设您的PLSQL 文件是巨大的,您的问题可能是由于您加载了the entire file into memory 这在这种情况下不是好方法,您应该read它逐行并将write 结果转换为temporary file,而不是将内容作为String 返回。

写起来有点复杂,但它确实是一种更可扩展的方法

【讨论】:

    【解决方案3】:

    您正在使用:

        strLine = strLine.replaceAll("--","\n--");
    

    然后您将写入字符串缓冲区,然后写入字符串生成器。

    由于您只想删除这些 cmets, 替换

        if(strLine.contains("--")){
            strLine = strLine.replaceAll("--","\n--");
         }
        strOut.append(strLine+"\n");
    

        int chk=strLine.indexOf("--");
          if(chk!=-1)
            strLine = strLine.subtring(0,chk);
        Out.append(strLine +"\n");
    

    希望这可以解决您的问题,因为您不会使用 StringBuffer 并使用更少的内存。

    【讨论】: