异常阅读非常大的文件> 300 MB

时间:2009-06-12 04:58:57

标签: java file

我的任务是在READ& WRITE模式下打开一个大文件,我需要通过搜索起点和终点搜索该文件中的部分文本。 然后我需要将搜索到的文本区域写入新文件,并从原始文件中删除该部分。

上述过程我会做更多次。 所以我认为对于这些过程,通过 CharBuffer 将文件加载到内存中会很容易,并且可以通过 MATCHER 类轻松搜索。 但是在阅读时我得到 HeapSpace异常,即使我通过执行如下所示增加到900MB java -Xms128m -Xmx900m readLargeFile 我的代码是

FileChannel fc = new FileInputStream(fFile).getChannel();
CharBuffer chrBuff = Charset.forName("8859_1").newDecoder().decode(fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()));
  

对于上面的代码每一个   建议我这是一个坏主意   将所有内容加载到内存和If中   文件大小是300 MB意味着,它将是   因charSet而600MB。

所以上面是我的任务,然后现在建议我一些高效方式。 请注意,我的文件大小会更强并且只使用 JAVA 才会执行这些操作。

提前致谢...

5 个答案:

答案 0 :(得分:5)

您绝对不希望使用Java将300MB文件加载到单个大型缓冲区中。对于大型文件而言,处理事务的方式应该比使用普通I / O更有效,但是当您对映射到内存中的整个文件运行Matcher时,您可以很容易地耗尽存储器中。

首先,你的代码存储器将文件映射到内存中...这将在你的虚拟地址空间中消耗300兆内存,因为文件是mmap,尽管这是在堆外。 (请注意,300 GB的虚拟地址空间被绑定,直到MappedByteBuffer被垃圾收集。请参阅下面的讨论。map的JavaDoc警告您。)接下来,您创建一个由此ByteBuffer ed文件支持的mmap。这应该没问题,因为它只是mmap ed文件的“视图”,因此应该占用最少的额外内存。它将是堆中的一个小对象,带有指向堆外部大对象的“指针”。接下来,您将其解码为CharBuffer,这意味着您创建300 MB缓冲区的副本,但是您在堆上创建了600 MB副本,因为{{1}是2个字节。

要回复评论,并查看JDK源代码以确定,当您以OP身份调用char时,您确实将整个文件映射到内存中。查看openJDK 6 b14 Windows本机代码map(),它首先调用CreateFileMapping,然后调用MapViewOfFile。查看此源代码,如果您要求将整个文件映射到内存中,此方法将完全按照您的要求执行。引用MSDN:

  

映射文件使文件的指定部分可见   调用进程的地址空间。

     

对于大于地址空间的文件,您只能映射一小部分   一次的文件数据。第一个视图完成后,您可以取消映射它   绘制新视图。

OP调用map的方式,文件的“指定部分”是整个文件。这不会导致耗尽,但它可能导致虚拟地址空间耗尽,这仍然是一个OOM错误。这可能会像耗尽堆一样彻底地终止你的应用程序。

最后,当您制作sun.nio.ch.FileChannelImpl.c时,Matcher可能会为此600 MB Matcher制作更多副本,具体取决于您使用它的方式。哎哟。这是少量对象使用的大量内存!每次CharBuffer ,您致电Matcher,您就会制作整个 toMatchResult()的{​​{1}}副本}。此外,每次您拨打String,最多只能制作整个CharBuffer的{​​{1}}副本。在最坏的情况下,你会使replaceAll()慢慢扩展到String结果的完整大小(在堆上施加大量内存压力),然后从CharBuffer创建StringBuffer这一点。

因此,如果您针对300 MB文件在replaceAll上调用String,并且找到了您的匹配项,那么您将首先制作一系列更大的replaceAll s直到你得到一个600 MB的。然后,您将制作此Matcher的{​​{1}}副本。这可以快速轻松地导致堆耗尽。

这是底线:StringBuffer未针对非常大的缓冲区进行优化。您可以非常轻松地制作许多非常大的物体,而无需计划。我在做类似于你正在做的事情并遇到内存耗尽的时候发现了这个,然后查看String的源代码。

注意:没有StringBuffer电话。一旦调用map,由Matcher绑定的堆外的虚拟地址空间就会卡在那里,直到Matcher被垃圾收集。因此,在对unmap进行垃圾回收之前,您将无法对该文件执行某些操作(删除,重命名,...)。如果在不同的文件上调用map足够的次数,但是堆中没有足够的内存压力来强制进行垃圾回收,那么堆外的内存可能就会内存不足。有关讨论,请参阅Bug 4724038

由于上述所有讨论,如果您将使用它来对大型文件制作MappedByteBuffer,并且您将在MappedByteBuffer上使用MappedByteBuffer,那么内存映射I / O可能不是可行的方法。它只会在堆上创建太多大对象,并在堆外部占用大量虚拟地址空间。在32位Windows下,JVM只有2GB(或者你已经更改了设置,3GB)的虚拟地址空间,这将在堆内外施加很大的内存压力。

我为这个答案的长度道歉,但我想要彻底。如果您认为上述任何部分是错误的,请发表评论并说出来。我不会做报复性的downvotes。我非常肯定上述所有内容都是准确的,但如果出现问题,我想知道。

答案 1 :(得分:2)

您的搜索模式是否匹配多行?如果没有,那么最简单的解决方案是逐行阅读:)。很简单

但是如果搜索模式匹配多行,那么您需要告诉我们,因为逐行搜索将无效。

答案 2 :(得分:0)

FileChannel.map将整个文件加载到内存中的声明是错误的,参考FileChannel.map()返回的MappedByteBuffer。它是一个“直接字节缓冲区”,它不会耗尽你的内存(直接字节缓冲区使用操作系统虚拟内存子系统根据需要将数据分页输入和输出内存,允许人们处理更大的内存块,因为它们是物理RAM。)但是再一次,单个MBB只适用于~2GB的文件。

试试这个:

FileChannel fc = new FileInputStream(fFile).getChannel();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());

CharBuffer chrBuff = mbb.asCharBuffer();

它不会将整个文件加载到内存中,而chrBuff只是后备MappedByteBuffer的视图,而不是副本。

但是,我不知道如何处理解码。

答案 3 :(得分:0)

使用缓冲区一次读取大量文件 有一个技巧: 每次将新字符串读入缓冲区时,请确保它具有长度为l的重叠,即子字符串的长度 l =长度(子串); 虽然(不是eof)这样做 开始    如果find(buffer,substring)返回TRUE;
   buffer [0..l] = substring;    buffer [l + 1,end] = read_new_chars_intobuffer; 端

答案 4 :(得分:-2)

就我而言,在类路径之后添加-Djava.compiler=NONE可以解决问题。