【问题标题】:Copying a java text file into a String将 java 文本文件复制到字符串中
【发布时间】:2011-01-25 01:08:59
【问题描述】:

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

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();

有人可以建议我为什么遇到堆空间错误吗?谢谢。

【问题讨论】:

  • 文件有多大?您的 JVM 内存设置 -Xms、-Xmx 是什么?任何 -XX 设置?
  • 大约 30MB。它可能比这更大。基本上,我写的是一个使用 Web 服务的客户端。客户端将字符串作为参数传递给 Web 服务。我目前没有更改我的 JVM 设置。

标签: java memory-leaks


【解决方案1】:

内存不足是因为您编写程序的方式需要将整个任意大的文件存储在内存中。您有 2 个选择:

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

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

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

编辑:要确定系统的初始和最大堆大小的默认值,您可以使用此代码 sn-p(我 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);  
     }    
}

【讨论】:

  • 堆大小 (KB):81280 最大堆大小 (KB):83392
  • @Asaph - 设置堆大小的问题是我在使用 Web 服务的客户端中使用此代码。客户端获取一个文件,将其转换为字符串并将其传递给 Web 服务。所以,我怀疑JVM选项有多大帮助。?谢谢。
  • 我有兴趣了解更多关于第二个选项的信息。您是在建议某种同步机制吗?
  • @Deepak Konidena:如果您正在处理从 Web 服务返回的 XML 数据,我建议使用 SAX 解析器。 SAX 解析器将 XML 逐个标记作为数据流处理,从而保持低内存占用。
  • @Asaph- 不,我需要将纯文本文件作为字符串发送。
【解决方案2】:
  • 您分配了一个越来越长的小 StringBuffer。根据文件大小预分配,你也会快很多。

  • 请注意,java 是 Unicode,字符串可能不是,所以你使用...内存大小的两倍。

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

【讨论】:

  • 文件大约 30MB。我尝试使用以字节为单位的文件大小预分配 StringBuffer,但它不会分配这么多。
  • @Deepak:如果你不能预分配到预期的大小,那么你当然不能增量读取它。您必须增加堆大小(正如 TomTom 指出的那样,这将至少增加一倍,因此您的 30MB 文件将需要至少 60MB 堆空间)。请注意,当您将其转换为字符串 (StringBuffer.toString()) 时,许多当前实现会创建一个新的 String,这意味着您需要再次加倍(即 120MB 堆空间)。或者,您可以通过某种方式逐步执行此操作。
【解决方案3】:

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

程序甚至还没有遇到下一次加倍——即 Java 6 中的 StringBuffer.toString() 将分配一个新的 String 并且内部的 char[] 将被再次复制(在一些早期版本的 Java 中,这不是案子)。在进行此副本时,您将需要双倍的堆空间 - 所以此时至少是实际文件大小的 4 倍(对于 byte->unicode 为 30MB * 2,然后对于 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

【讨论】:

    【解决方案4】:

    Kris 可以解决您的问题。

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

    【讨论】:

      【解决方案5】:

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

      • 创建您的 StringBuffer,其初始容量为您正在读取的字符串的大小
      • 最后关闭文件阅读器:fr.close();

      【讨论】:

        【解决方案6】:

        默认情况下,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 参数。

        【讨论】:

        • @Kris。谢谢。我的 prog 也适用于小文件。只是我无法调整 JVM 选项,因为这段代码进入接受可变大小文件的客户端,理想情况下应该将它们转换为字符串,将它们传递给 web 服务。
        【解决方案7】:

        尝试将任意大的文件读入应用程序的主内存是糟糕的设计。时期。再多的 JVM 设置调整/等等...都无法解决这里的核心问题。我建议您休息一下,在 Google 上搜索并阅读有关如何在 java 中处理流的信息 - 这里有一个很好的 tutorial 和另一个 good tutorial 来帮助您入门。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-05-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-01-27
          • 1970-01-01
          • 2016-07-30
          相关资源
          最近更新 更多