OutOfMemoryError读取具有大行的174 Mb文本文件

时间:2019-03-27 11:23:49

标签: java file bufferedreader

我有一个包含12000行的csv文件。 每行都有几个用双引号引起来并用逗号分隔的字段。此字段之一是xml文档,因此该行可能很长。文件大小为174 Mb。

以下是文件的示例:

"100000","field1","field30","<root><data>Hello I have a
line break</data></root>","field31"
"100001","field1","field30","<root><data>Hello I have multiple
line 
break</data></root>","field31"

此文件的问题在xml字段内,该字段可能具有一个或多个换行符,因此可能会中断解析。这里的目标是读取整个文件并应用一个正则表达式,它将用空字符串替换双引号内的所有换行符。

以下代码给了我OutOfMemoryError:

    String path = "path/to/file.csv";

    try {
        byte[] content = Files.readAllBytes(Paths.get(path));
    }
    catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
    }

我还尝试使用BufferedReader和StringBuilder读取文件,在第5000行出现OutOfMemoryError:

String path = "path/to/file.csv";

    try {
        StringBuilder sb = new StringBuilder();
        BufferedReader br = new BufferedReader(new FileReader(path));
        String line;
        int count = 0;
        while ((line = br.readLine()) != null) {
            sb.append(line);
            System.out.println("Read " + count++);
        }
    }
    catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
    }

我尝试使用不同的Java堆值(例如-Xmx1024m,-Xmx4096m,-Xmx8092m)运行以上两个程序。在所有情况下,我都遇到了OutOfMemoryError。 考虑到文件大小为174Mb,为什么会发生这种情况?

3 个答案:

答案 0 :(得分:3)

您需要使用双缓冲区来解析您的特殊数据结构,并逐行处理它们。阅读整个文档不是最好的主意。

创建一个自己的BufferedReader,该行读取带有CSV文件内部BufferedReader的行。 读取一行后,尝试确定是否需要阅读更多行以完成CSV中的一行(例如,如果您知道XML以<root>开头和以</root>结尾,请检查是否存在这些行字符串,然后读取并追加直到您到达结束标记-这将是CSV行的最后一行)。

第二层将是CSV处理,基于您从第一步获得的CSV行。对其进行解析,保存,处理,然后将其抛出。这样它就不会消耗更多的内存空间,Java垃圾收集器将释放它。

这是处理大文件的唯一方法。它也称为“流模型”,因为您只传递一小部分数据,因此实际内存消耗较低。

答案 1 :(得分:2)

用过滤器将InputStream包裹起来:

class QuotedNewLineFilterInputStream extends FilterInputStream {

    private boolean insideQuotes;

    public QuotedNewLineFilterInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int c = super.read();
        if (c == '\"') {
            insideQuotes = !insideQuotes;
        }
        if (insideQuotes && (c == '\n' || c == '\r')) {
            c = read();
        }
        return c;
    }
}

这将删除双引号内的LF和CR。由于全部都是ASCII,并且XML可能在UTF-8中使用,因此可以在字节级别(InputStream)上工作。

\t替代可能会更好地保留布局(c = \ t'i.o. c = read())。

不是很聪明,但是简单的解决方案。

答案 2 :(得分:0)

如果使用Files.readAllBytes(Paths.get(path));读取174 MB的文件导致OutOfMemoryError,则无法通过-Xmx8g增加内存限制。有了8 GB的堆内存,为byte[]

分配174 MB的连续内存应该没有问题。

再次检查您如何通过-Xmx标志。您可以通过使用JConsole,JVisualVM或其他工具连接到正在运行的JVM证明来验证JVM运行时选项。看一下Using JConsole,它显示了如何检查JVM运行时选项,例如内存标签。