15

我有一个包含 2000 万行文本的大型文本文件。当我使用以下程序读取文件时,它工作得很好,实际上我可以读取更大的文件而没有内存问题。

public static void main(String[] args) throws IOException {
    File tempFile = new File("temp.dat");
    String tempLine = null;
    BufferedReader br = null;
    int lineCount = 0;
    try {
        br = new BufferedReader(new FileReader(tempFile));
        while ((tempLine = br.readLine()) != null) {
            lineCount += 1;
        }
    } catch (Exception e) {
        System.out.println("br error: " +e.getMessage());
    } finally {
        br.close();
        System.out.println(lineCount + " lines read from file");
    }
}

但是,如果我需要在读取该文件之前将一些记录附加到该文件中,则 BufferedReader 会消耗大量内存(我刚刚使用 Windows 任务管理器对此进行了监控,我知道这不是很科学,但它证明了问题所在)。修改后的程序如下,与第一个相同,只是我先将一条记录附加到文件中。

public static void main(String[] args) throws IOException {
    File tempFile = new File("temp.dat");
    PrintWriter pw = null;
    try {
        pw = new PrintWriter(new BufferedWriter(new FileWriter(tempFile, true)));
        pw.println(" ");
    } catch (Exception e) {
        System.out.println("pw error: " + e.getMessage());
    } finally {
        pw.close();
    }

    String tempLine = null;
    BufferedReader br = null;
    int lineCount = 0;
    try {
        br = new BufferedReader(new FileReader(tempFile));
        while ((tempLine = br.readLine()) != null) {
            lineCount += 1;
        }
    } catch (Exception e) {
        System.out.println("br error: " +e.getMessage());
    } finally {
        br.close();
        System.out.println(lineCount + " lines read from file");
    }
}

Windows 任务管理器的屏幕截图,其中行中的大凸起显示了我运行该程序的第二个版本时的内存消耗。

任务管理器截图

所以我能够读取这个文件而不会耗尽内存。但是我有超过 5000 万条记录的更大文件,当我对它们运行这个程序时遇到内存不足异常?有人可以解释为什么该程序的第一个版本适用于任何大小的文件,但第二个程序的行为如此不同并以失败告终?我在 Windows 7 上运行:

java 版本 "1.7.0_05"
Java(TM) SE Runtime Environment (build 1.7.0_05-b05)
Java HotSpot(TM) Client VM (build 23.1-b03, 混合模式, 共享)

4

6 回答 6

1

您可以使用VM-Options启动 Java-VM

-XX:+HeapDumpOnOutOfMemoryError

这会将堆转储写入文件,可以对其进行分析以查找泄漏嫌疑人

使用“+”添加选项,使用“-”删除选项。

如果您使用 Eclipse Java Memory Analyzer Plugin MAT从正在运行的 VM 中获取堆转储,并对 Leak Suspects 等进行一些很好的分析。

于 2012-09-11T21:38:25.557 回答
0

这可能是因为您的文件中可能根本没有换行符/回车符。在这种情况下,readLine()尝试从您的文件中仅创建一个字符串,该字符串可能内存不足。

readLine() 的 Java 文档:

Reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.

于 2012-09-09T11:13:25.410 回答
0

你有没有尝试过:

A)创建一个新的 File 实例用于读取,但指向同一个文件。B)在第二部分中读取一个完全不同的文件。

我想知道 File 对象是否仍然以某种方式附加到 PrintWriter,或者操作系统是否对文件句柄做了一些有趣的事情。这些测试应该告诉你应该把重点放在哪里。

这看起来不是代码的问题,你认为它不应该中断的逻辑似乎是合理的,所以它必须是一些底层功能。

于 2012-09-10T15:56:57.143 回答
0
     pw = new PrintWriter(new BufferedWriter(new FileWriter(tempFile, true)));

你试过不使用 BufferedWriter 吗?如果您在末尾附加几行,也许您不需要缓冲区?如果这样做,请考虑使用字节数组(集合或字符串构建器)。最后你在 java 1.6_32 中尝试过同样的方法吗?可能是其中一位作家的新版本中的错误。

你能在 pw.close() 前后打印空闲内存吗??

System.out.println("before wr close :"  + Runtime.getRuntime().freeMemory());

关闭后和阅读器关闭后类似

于 2012-09-09T11:07:09.300 回答
0

每次执行 Java 后的 Java 例程时,都会创建一个全新的对象:

tempLine = br.readLine()

我相信每次调用 readLine() 时,它可能会创建一个新的 String 对象,每次调用重新分配以将值分配给 tempLine 时,该对象都会留在堆上。

因此,由于 GC 不会一直被调用,因此数以千计的对象可以在几秒钟内留在堆上。

有人说每 1000 行左右调用 System.gc() 是个坏主意,但我很好奇这是否能解决您的问题。此外,您可以在每一行之后运行此命令,以基本上将每个对象标记为可垃圾回收:

tempLine=null;
于 2012-08-30T23:44:50.123 回答
-3

你需要用更大的堆启动java。尝试将 -Xmx1024m 作为 java 命令的参数。

基本上你需要比文件大小更多的内存。

于 2012-08-30T17:31:07.717 回答