如何处理大文本文件的阅读和处理,而不会得到OutofMemoryError

时间:2013-04-16 13:53:48

标签: java

我写了一些简单的代码来阅读文本文件(> 1g)并对字符串进行一些处理。

但是,我必须处理Java堆空间问题,因为我尝试追加Strings(使用StringBuilder),它们在某些时候对内存使用有很大的影响。我知道我可以增加我的堆空间,e。 G。 '-Xmx1024',但我想在这里只使用很少的内存。我如何更改下面的代码来管理我的操作?

我仍然是Java新手,也许我在代码中犯了一些错误,这对你来说显而易见。

以下是代码段:

    private void setInputData() {

    Pattern pat = Pattern.compile("regex");
    BufferedReader br = null;
    Matcher mat = null;

    try {
        File myFile = new File("myFile");
        FileReader fr = new FileReader(myFile);

        br = new BufferedReader(fr);
        String line = null;
        String appendThisString = null;
        String processThisString = null;
        StringBuilder stringBuilder = new StringBuilder();

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

            mat = pat.matcher(line);

            if (mat.find()) {
                appendThisString = mat.group(1);
            }

            if (line.contains("|")) {
                processThisString = line.replace(" ", "").replace("|", "\t");
                stringBuilder.append(processThisString).append("\t").append(appendThisString);
                stringBuilder.append("\n");
            }
        }
//      doSomethingWithTheString(stringBuilder.toString());
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        try {
            if (br != null)br.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

以下是错误消息:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2367)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:130)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:114)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:415)
    at java.lang.StringBuilder.append(StringBuilder.java:132)
    at Test.setInputData(Test.java:47)
    at Test.go(Test.java:18)
    at Test.main(Test.java:13)

7 个答案:

答案 0 :(得分:1)

在这种情况下,您无法使用StringBuilder。它将数据保存在内存中。 我认为你应该考虑将结果保存到每一行的文件中。

即。使用FileWriter而不是StringBuilder。

答案 1 :(得分:1)

一般策略是设计你的应用程序,使其不需要在内存中保存整个文件(或者它的一大部分)。

取决于您的应用程序的功能:

  • 您可以将中间数据写入文件,然后一次又一行地读回来处理它。
  • 您可以将每行读取传递给处理算法;例如通过在每一行上单独调用doSomethingWithTheString(...)而不是全部调用它们。

但是如果你需要将整个文件放在内存中,那么你就是在摇滚和困难之间。


另一件需要注意的是,使用类似StringBuilder的内存可能需要高达文件大小的6倍内存。它是这样的。

  • StringBuilder需要扩展其内部缓冲区时,它通过将char数组设置为当前缓冲区大小的两倍,并从旧模块复制到新缓冲区来实现此目的。此时,在缓冲区扩展开始之前,您分配的缓冲区空间是3倍。现在假设还有一个字符要附加到缓冲区。

  • 如果文件是ASCII(或另一个8位字符集),StringBuilder的缓冲区需要两倍的内存...因为它由char而不是{{ 1}}值。

如果您对最终字符串中的字符数有一个很好的估计(例如从文件大小),则可以在创建byte时通过提供容量提示来避免x3乘数。但是,你不能低估,“因为你只是略微低估了......”

您也可以使用面向字节的缓冲区(例如StringBuilder)而不是StringBuilder ...然后使用ByteArrayOutputStream / ByteArrayInputStream / {{1}来读取它管道。

但最终,随着文件大小的增加,在内存中保存一个大文件并不会扩展。

答案 2 :(得分:1)

从你的例子中可以看出,一旦修改了你的巨大字符串,你将会怎么做。但是,由于您的修改似乎不跨越多行,我只是将修改后的数据写入新文件。

为了在FileWriter周期之前创建并打开新的while对象,请将stringBuffer声明移至周期的开头并将stringBuffer写入在周期结束时你的新文件。

另一方面,如果您需要组合来自不同行的数据,请考虑使用数据库。哪种取决于您的数据的性质。如果它具有类似记录的组织,您可以采用关系数据库,例如Apache DerbyMySQL,否则您可能会检出所谓的无SQL数据库,例如Cassandra或{{ 3}}

答案 3 :(得分:1)

你可以在不附加的情况下进行干运行,但计算总的字符串长度。

如果doSomethingWithTheString是顺序的,那么就会有其他解决方案。

您可以对字符串进行标记,从而减小尺寸。例如,Huffman压缩查找已经存在的读取char的序列,可能扩展表,然后产生表索引。 (开源的OmegaT翻译工具在一个地点使用这种策略作为令牌。)所以这取决于你想要做的处理。看到一种CSV的阅读字典似乎是可行的。

一般情况下,我会使用数据库。

P.S。您可以节省一半内存,将所有内容写入文件,然后将文件重新读取到一个字符串中。或者在文件上使用java.nio ByteBuffer,这是一个内存映射文件。

答案 4 :(得分:1)

方法doSomethingWithTheString()可能需要更改,以便它也接受InputStream。在读取原始文件内容并逐行转换时,您应该将转换后的内容逐行写入临时文件。然后,可以将该临时文件的输入流发送到doSomethingWithTheString()方法。可能需要将该方法重命名为doS​​omethingWithInputStream()。

答案 5 :(得分:0)

你确定文件中有一个行终止符吗?如果没有,你的while循环将继续循环并导致你的错误。如果是这样,可能值得一次尝试读取固定数量的字节,这样读者就无法无限增长。

答案 6 :(得分:0)

我建议使用Guavas FileBackedOutputStream。你获得了一个OutputStream的优势,它将占用磁盘io而不是主内存。当然,由于磁盘io访问速度会变慢,但是,如果你正在处理这么大的流,并且你无法将其分成更大的可管理大小,那么这是一个不错的选择。