将java文本文件复制到String中

时间:2010-03-08 15:58:48

标签: java memory-leaks

当我尝试将大文件存储到字符串中时,我遇到以下错误。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2882)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:515)
    at java.lang.StringBuffer.append(StringBuffer.java:306)
    at rdr2str.ReaderToString.main(ReaderToString.java:52)

很明显,我的堆空间已经用完了。基本上我的pgm看起来像这样。

FileReader fr = new FileReader(<filepath>);
sb = new StringBuffer();
char[] b = new char[BLKSIZ];

while ((n = fr.read(b)) > 0) 
     sb.append(b, 0, n);    

fileString = sb.toString();

有人可以告诉我为什么我遇到堆空间错误?感谢。

7 个答案:

答案 0 :(得分:4)

由于编写程序的方式,内存不足,需要将整个任意大的文件存储在内存中。您有两个选择:

  • 您可以通过将命令行开关传递给JVM来增加内存:

    java -Xms<initial heap size> -Xmx<maximum heap size>
    
  • 您可以重写逻辑,以便在流入时处理文件数据,从而使程序的内存占用率保持在较低水平。

我推荐第二个选项。这是更多的工作,但这是正确的方法。

编辑:要确定系统的初始和最大堆大小的默认值,您可以使用此代码段(我stole from a JavaRanch thread):

public class HeapSize {    
     public static void main(String[] args){      
         long kb = 1024;  
         long heapSize = Runtime.getRuntime().totalMemory();    
         long maxHeapSize = Runtime.getRuntime().maxMemory();  
         System.out.println("Heap Size (KB): " + heapSize/1024);  
         System.out.println("Max Heap Size (KB): " + maxHeapSize/1024);  
     }    
}

答案 1 :(得分:2)

  • 您分配了一个越来越长的小StringBuffer。根据文件大小预先分配,你也会更快。

  • 请注意,java是Unicode,字符串可能不是,因此您使用的内存是内存的两倍。

  • 根据VM(32位?64位?)和限制设置(http://www.devx.com/tips/Tip/14688),您可能没有足够的可用内存。文件实际有多大?

答案 2 :(得分:1)

默认情况下,Java以最小的堆开始(至少在Windows上为64M)。您是否有可能尝试读取太大的文件?

如果是这样,您可以使用JVM参数-Xmx256M增加堆(将最大堆设置为256 MB)

我尝试运行稍微修改过的代码版本:

public static void main(String[] args) throws Exception{
    FileReader fr = new FileReader("<filepath>");
    StringBuffer sb = new StringBuffer();
    char[] b = new char[1000];
    int n = 0;
    while ((n = fr.read(b)) > 0) 
         sb.append(b, 0, n);    

    String fileString = sb.toString();
    System.out.println(fileString);
}

在一个小文件(2 KB)上,它按预期工作。您需要设置JVM参数。

答案 3 :(得分:1)

克里斯有你的问题的答案。

您还可以查看java commons fileutils' readFileToString,这可能会更有效率。

答案 4 :(得分:1)

虽然这可能无法解决您的问题,但您可以做一些小事来使您的代码更好一些:

  • 使用您正在阅读的字符串
  • 的初始容量创建StringBuffer
  • 最后关闭你的文件阅读器:fr.close();

答案 5 :(得分:1)

在OP中,您的程序在StringBuffer展开时正在中止。您应该将其预分配到您需要的尺寸或至少接近它。当StringBuffer必须扩展时,它需要RAM用于原始容量和新容量。正如TomTom所说,你的文件可能是8位字符,因此将在内存中转换为16位unicode,因此它的大小将加倍。

该程序甚至还没有遇到下一次加倍 - 即Java 6中的StringBuffer.toString()将分配新的String,内部char[]将再次被复制(在某些早期版本中) Java的情况并非如此)。在这个副本的时候你就需要双倍的堆空间 - 所以在那一刻,至少4倍的实际文件大小(30MB * 2字节 - &GT什么; unicode的,然后的toString 60MB * 2()调用= 120MB )。完成此方法后,GC将清理临时类。

如果无法为程序增加堆空间,则会遇到一些困难。你不能采取“简单”的路线,只需返回String。您可以尝试逐步执行此操作,这样您就不必担心文件大小(最佳解决方案之一)。

查看客户端中的Web服务代码。它可以提供一种方法来使用String以外的其他类 - 可能是java.io.Readerjava.lang.CharSequence或特殊接口,例如与SAX相关的org.xml.sax.InputSource。这些中的每一个都可用于构建一个实现类,它可以在调用者需要时以块的形式从文件中读取,而不是一次加载整个文件。

例如,如果您的Web服务处理路由可以采用CharSequence然后(如果它们写得很好)您可以创建一个特殊的处理程序,一次只从文件返回一个字符 - 但缓冲输入。看到这个类似的问题:How to deal with big strings and limited memory

答案 6 :(得分:1)

尝试将任意大的文件读入应用程序的主内存是不好的设计。期。没有任何JVM设置调整/等等......将在这里解决核心问题。我建议你休息一下,做一些谷歌搜索和阅读如何在java中处理流 - 这是一个很好的tutorial,这里是另一个good tutorial来帮助你入门。