exception读取非常大的文件> 300 MB

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

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

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

对于上面的代码,每个人都建议我将所有内容加载到内存中是一个坏主意,如果文件大小为300 MB则意味着,由于charSet,它将是600MB。

所以上面是我的任务,然后现在建议我一些有效的方法。 请注意, 我的文件大小会更多 ,只使用JAVA我会做这些事情。

提前致谢…

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

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

要响应注释,并查看JDK源代码以确定,当您调用map()作为OP时,您实际上将整个文件映射到内存中。 查看openJDK 6 b14 Windows本机代码sun.nio.ch.FileChannelImpl.c ,它首先调用CreateFileMapping ,然后调用MapViewOfFile 。 看一下这个来源,如果你要求将整个文件映射到内存中,这个方法就会按照你的要求完成。 引用MSDN:

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

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

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

最后,当你制作一个MatcherMatcher可能会制作这个600 MB CharBuffer更多副本,具体取决于你如何使用它。 哎哟。 这是少量对象使用的大量内存! 给定一个Matcher每次调用toMatchResult() ,你都会制作整个 CharBufferString副本。 此外, 每次调用replaceAll() ,最多都会生成整个CharBufferString副本。 在最坏的情况下,你将生成一个StringBuffer ,它将慢慢扩展到replaceAll结果的完整大小(在堆上施加大量内存压力),然后从中生成一个String

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

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

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

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

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

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

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

声称FileChannel.map将整个文件加载到内存中是错误的,参考FileChannel.map()返回的MappedByteBuffer 。 它是一个“直接字节缓冲区”,它不会耗尽你的内存(直接字节缓冲区使用操作系统虚拟内存子系统根据需要将数据分入和分析内存,允许人们处理更大的内存块,因为它们是物理内存。 )但是再一次,单个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的视图 ,而不是副本。

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

使用缓冲区一次读取大量文件有一个技巧:每次读取一个新的字符串到缓冲区时,确保它有一个长度为l的重叠,这是子串的长度l = length(substring); while(not eof)如果find(buffer,substring)返回TRUE则开始;
buffer [0..l] = substring; buffer [l + 1,end] = read_new_chars_intobuffer; 结束

在我的例子中,在类路径之后添加-Djava.compiler=NONE可以解决问题。