繁体   English   中英

如何在不获取OutofMemoryError的情况下处理读取和处理大型文本文件

[英]How to deal with reading and processing huge text files without getting OutofMemoryError

我编写了一些简单的代码来读取文本文件(> 1g)并对String进行一些处理。

但是,我必须处理Java堆空间问题,因为我尝试附加String(使用StringBuilder),这有时会占用更多的内存。 我知道我可以使用'-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)

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

即使用FileWriter而不是StringBuilder。

一般的策略是设计您的应用程序,以便它不需要将整个文件(或文件的太大比例)保存在内存中。

根据您的应用程序执行以下操作:

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

但是,如果您需要将整个文件存储在内存中,那么您将陷入困境。


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

  • StringBuilder需要扩展其内部缓冲区时,它可以通过使char数组的大小是当前缓冲区的两倍,并从旧版本复制到新版本来完成此操作。 那时,您分配的缓冲区空间是缓冲区扩展开始之前的3倍。 现在,假设仅一个字符要追加到缓冲区。

  • 如果文件是ASCII(或其他8位字符集)格式,则StringBuilder的缓冲区需要两倍的内存...,因为它是由char而不是byte值组成的。

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

您也可以使用面向字节的缓冲区(例如ByteArrayOutputStream )代替StringBuilder ...,然后使用ByteArrayInputStream / StreamReader / BufferedReader管道读取它。

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

从您的示例中,尚不清楚一旦修改了巨大的字符串,您将如何处理它。 但是,由于您的修改似乎没有跨越多行,因此我只将修改后的数据写入新文件。

为了做到这一点,在while循环之前创建并打开一个新的FileWriter对象,请将stringBuffer声明移至循环的开始,并在循环结束时将stringBuffer写入新文件。

另一方面,如果您确实需要合并来自不同行的数据,请考虑使用数据库。 哪种类型取决于数据的性质。 如果它具有类似记录的组织,则可以采用关系数据库,例如Apache DerbyMySQL ,否则您可以签出所谓的No SQL数据库,例如CassandraMongoDB

您可以进行空运行,而无需附加,但要计算字符串的总长度。

如果doSomethingWithTheString是顺序的,将有其他解决方案。

您可以标记字符串,以减小大小。 例如,霍夫曼压缩查找已存在的读取char的序列,可能扩展表,然后产生表索引。 (开放源代码的OmegaT转换工具在一处针对令牌使用这种策略。)因此,这取决于您要执行的处理。 看到一种CSV的阅读字典似乎是可行的。

通常,我会使用数据库。

PS,您可以节省一半的内存,全部写入一个文件,然后以一个字符串重新读取该文件。 或在文件(使用内存映射的文件)上使用java.nio ByteBuffer。

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

您确定文件中有行终止符吗? 如果没有,您的while循环将一直循环并导致错误。 如果是这样,可能值得尝试一次读取固定数量的字节,以使读取器不会无限增长。

我建议使用番石榴FileBackedOutputStream。 您将获得拥有OutputStream的优势,它将占用磁盘io而不是主内存。 当然,由于磁盘io,访问速度会变慢,但是,如果要处理如此大的流,并且无法将其分块为更大的可管理大小,则这是一个不错的选择。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM