在Java中读取大文件 - Java堆空间

时间:2011-05-04 22:30:57

标签: java file file-io

我正在阅读一个大的tsv文件(~40G),并试图通过逐行读取并仅将某些行打印到新文件来修剪它。但是,我一直得到以下例外:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2894)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:117)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:532)
    at java.lang.StringBuffer.append(StringBuffer.java:323)
    at java.io.BufferedReader.readLine(BufferedReader.java:362)
    at java.io.BufferedReader.readLine(BufferedReader.java:379)

以下是代码的主要部分。为了以防万一,我将缓冲区大小指定为8192。达到缓冲区大小限制后,Java是否清除缓冲区?我没有看到可能导致大量内存使用的原因。我试图增加堆大小但它没有任何区别(机器有4GB RAM)。我也试过每X行刷一次输出文件,但它也没有帮助。我想也许我需要打电话给GC,但听起来不对。

有什么想法?非常感谢。 顺便说一句 - 我知道我应该只调用一次trim(),存储它,然后再使用它。

Set<String> set = new HashSet<String>();
set.add("A-B");
...
...
static public void main(String[] args) throws Exception
{
   BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile),"UTF-8"), 8192);
   PrintStream output = new PrintStream(outputFile, "UTF-8");

   String line = reader.readLine();
   while(line!=null){
        String[] fields = line.split("\t");
        if( set.contains(fields[0].trim()+"-"+fields[1].trim()) )
            output.println((fields[0].trim()+"-"+fields[1].trim()));

        line = reader.readLine();
   }

output.close();

}

5 个答案:

答案 0 :(得分:17)

最有可能的是,该文件没有行终止符,因此读者只是不断增长它的StringBuffer无限制,直到内存不足为止。

解决方案是使用阅读器的“读取”方法一次读取固定数量的字节,然后在较小的缓冲区内查找新行(或其他解析令牌)。

答案 1 :(得分:3)

您确定文件中的“行”是否由换行符分隔?

答案 2 :(得分:2)

我有3个理论:

  • 输入文件不是UTF-8,而是一些不确定的二进制格式,当读取为UTF-8时会产生极长的行。

  • 该文件包含一些非常长的“行”......或者根本没有换行符。

  • 您没有向我们展示的代码中还发生了其他事情;例如您要向set添加新元素。


帮助诊断:

  • 使用od之类的工具(在UNIX / LINUX上)确认输入文件确实包含有效的行终止符;即CR,NL或CR NL。
  • 使用某些工具检查文件是否有效UTF-8。
  • 为您的代码添加一个静态行计数器,当应用程序与OOME一起爆炸时,打印出行计数器的值。
  • 跟踪到目前为止看到的最长线,并在你得到OOME时打印出来。

根据记录,您对trim的略微不理想的使用与此问题无关。

答案 3 :(得分:1)

一种可能性是,在垃圾收集期间,您的堆空间用完了。默认情况下,Hotspot JVM使用并行收集器,这意味着您的应用程序可能比收集器可以回收它们更快地分配对象。通过快速分配和丢弃,我已经能够通过假定只有10K实时(小)对象导致OutOfMemoryError。

您可以尝试使用带有-XX:+UseSerialGC选项的旧(1.5之前)串行收集器。还有其他几个&#34;扩展&#34; options,您可以使用它来调整集合。

答案 4 :(得分:-1)

您可能想尝试从循环中删除String[] fields声明。在每个循环中创建一个新数组时。你可以重复使用旧的吗?