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

在Java中读取大文件--Java堆空间,java,file,file-io,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 j

我正在读取一个大的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();

}
Set Set=newhashset();
集合。添加(“A-B”);
...
...
静态公共void main(字符串[]args)引发异常
{
BufferedReader=new BufferedReader(new InputStreamReader(new FileInputStream(inputFile),“UTF-8”),8192;
PrintStream输出=新的PrintStream(输出文件,“UTF-8”);
字符串行=reader.readLine();
while(行!=null){
String[]fields=line.split(“\t”);
if(set.contains(字段[0].trim()+“-”+字段[1].trim()))
println((字段[0].trim()+“-”+字段[1].trim());
line=reader.readLine();
}
output.close();
}

最有可能的情况是,该文件没有行终止符,因此读卡器只是不断增加它的StringBuffer,直到内存耗尽为止


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

您确定文件中的“行”由新行分隔吗?

您可能希望尝试从循环中删除
字符串[]字段
声明。在每个循环中创建新数组时。你可以重复使用旧的,对吗?

我有三个理论:

  • 输入文件不是UTF-8,而是一些不确定的二进制格式,当读取为UTF-8时会导致非常长的行

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

  • 代码中发生了一些您没有向我们展示的其他事情;e、 g.您正在将新元素添加到
    集合


要帮助诊断此问题,请执行以下操作:

  • 使用诸如
    od
    (在UNIX/LINUX上)之类的工具确认输入文件确实包含有效的行终止符;i、 e.CR、NL或CR NL
  • 使用一些工具检查文件是否为有效的UTF-8
  • 在代码中添加一个静态行计数器,当应用程序出现OOME时,打印出行计数器的值
  • 记录到目前为止看到的最长的一行,并在收到OOME时打印出来


请注意,您对
trim
的使用稍微不太理想,这与此问题无关。

一种可能是在垃圾收集过程中堆空间耗尽。Hotspot JVM默认使用并行收集器,这意味着您的应用程序分配对象的速度可能快于收集器回收对象的速度。通过快速分配和丢弃,我已经能够在假设只有10K个活动(小)对象的情况下导致OutOfMemory错误


您可以尝试使用旧的(1.5之前版本)串行采集器,并选择
-XX:+UseSerialGC
。您可以使用其他几个“扩展”来调整集合。

它是否打印任何结果?它总是在同一点爆炸吗?你确定它实际上一次读取一行文件,也就是说,它确定行结束了,没有足够长的行导致堆爆炸?要问的愚蠢问题我知道…您在JVM上使用的是什么-Xmx设置?默认情况下,Java不会使用计算机上所有可用的RAM,除非您指定使用-Xmx参数。@bstick12:为应用程序提供更多内存可能会隐藏一些重要的设计错误,这些错误将在以后弹出。在99.99%的情况下,默认内存是足够的,如果没有,那么你就做错了“在99.99%的情况下,默认内存是足够的,如果没有,那么你就做错了”那么你如何解释大多数生产服务器以及在某种程度上许多java应用程序都增加了默认值?99.99%的论文做错了什么?他没有创造任何东西。他正在声明一个变量,该变量包含对字符串对象数组的引用(由
split()
返回)。由于它所需的作用域仅在循环中,因此可以在那里声明它。字符串[]是循环作用域中的一个局部变量,JVM将垃圾收集为数组分配的任何内存。@BrianRoach如果我错了,请纠正我,但每次
split()都会创建一个
String[]
被调用了吗?我知道@Shaunak来自哪里——如果在每个循环上都创建了一个
String[]
,那么在循环之前声明它,在每次迭代中重复使用它,然后在循环之后将它设置为null(对于GC)不是更有效吗?(我确信这是J2ME时代的教学方式!…)这可能是NIO软件包的好地方-他需要处理40GB左右的文本数据所能获得的所有性能。现在这很有意义,因为我注意到,不管我设置的最大堆大小如何,最终输出文件大小总是相同的。所以我怀疑有一条线引起了麻烦。我现在正在检查。非常感谢@用户431336:另外,别忘了关闭打印流。。。当您终止该方法时,您的示例将其保留为打开状态。@Dataknife打印流?一旦循环终止,我就会关闭它。首先要检查的是缺少行终止符的损坏文件——我在读取4Gb时遇到了完全相同的情况