首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >当读取非常大的文件> 300 MB时出现异常

当读取非常大的文件> 300 MB时出现异常
EN

Stack Overflow用户
提问于 2009-06-12 04:58:56
回答 5查看 3K关注 0票数 0

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

以上过程我会多做一次。因此,我认为对于这些过程,通过CharBuffer将文件加载到内存中是很容易的,并且可以通过MATCHER类轻松地搜索。但是我在阅读时得到了HeapSpace异常,尽管我通过执行java -Xms128m -Xmx900m readLargeFile来增加到900 My,但我的代码是

代码语言:javascript
复制
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,它将是600 MB。

因此,以上是我的任务,然后现在建议我一些有效的方法。请注意,我的文件大小将更多,只需使用JAVA就可以完成这些工作。

提前谢谢..。

EN

回答 5

Stack Overflow用户

发布于 2009-06-12 05:10:25

您肯定不希望使用Java将300 to文件加载到单个大缓冲区中。对于大型文件,您的工作方式应该比使用普通的I/O更有效,但是当您对映射到内存中的整个文件运行Matcher时,您可以非常容易地耗尽内存。

首先,代码内存将文件映射到内存中.这将占用您虚拟地址空间中的300 Meg内存,因为文件是mmap编辑到它中的,尽管这是堆之外的。(请注意,300 Meg的虚拟地址空间被占用,直到垃圾收集到MappedByteBuffer为止。讨论情况见下文。JavaDoc for map下一步,您将创建一个由mmaped文件支持的ByteBuffer。这应该很好,因为它只是mmaped文件的一个“视图”,因此应该占用最少的额外内存。它将是堆中的一个小对象,其中有一个指向堆外的大对象的“指针”。接下来,您将其解码为CharBuffer,这意味着您为300 MB缓冲区创建了一个副本,但是您(在堆上)创建了一个600 MB的副本,因为char是2个字节。

为了响应注释,并查看JDK源代码以确保,当您像OP那样调用map()时,实际上可以将整个文件映射到内存中。查看openJDK 6 b14 Windows本机代码sun.nio.ch.FileChannelImpl.c,它首先调用CreateFileMapping,然后调用MapViewOfFile。查看此源,如果您要求将整个文件映射到内存中,此方法将完全按照您的要求执行。引用MSDN:

映射文件使文件的指定部分在调用进程的地址空间中可见。 对于大于地址空间的文件,只能一次映射文件数据的一小部分。当第一个视图完成后,您可以取消映射并映射一个新视图。

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

最后,当您制作一个Matcher时,Matcher可能会产生更多的600 MB CharBuffer的副本,这取决于您如何使用它。唉哟。这是一小部分对象使用的大量内存!给定一个Matcher,每当您调用toMatchResult()时,您都会生成整个 CharBufferString副本。而且,每次调用时,最多都会生成整个CharBufferString副本。最坏的情况是,您将创建一个StringBuffer,它将慢慢扩展到replaceAll结果的全部大小(在堆上施加大量内存压力),然后根据这个结果生成一个String

因此,如果您针对一个300 MB的文件在一个Matcher上调用Matcher,并且找到了您的匹配项,那么您将首先做出一系列更大的StringBuffer,直到得到一个600 MB的StringBuffer。然后,您将制作这个StringBuffer的一个StringBuffer副本。这可以快速而容易地导致堆耗尽。

这里的底线是:Matcher不是为处理非常大的缓冲区而优化的。你可以很容易地,而且没有计划,制造一些非常大的对象。当我做一些与您所做的非常相似的事情并遇到内存耗尽时,我发现了这一点,然后查看了Matcher的源代码。

注意:没有unmap调用。调用map后,由MappedByteBuffer绑定的堆外的虚拟地址空间就会被卡在那里,直到垃圾收集到MappedByteBuffer为止。因此,您将无法对文件执行某些操作(删除、重命名、.)直到MappedByteBuffer被垃圾收集。如果在不同的文件上调用map足够多次,但堆中没有足够的内存压力来强制垃圾收集,则可以从堆之外的内存中抽出。有关讨论,请参见Bug 4724038

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

我对这个答复的篇幅表示歉意,但我想彻底解释一下。如果你认为上述任何部分是错误的,请评论并说出来。我不会做报复性否决。我非常肯定,以上所有的一切都是准确的,但如果有什么问题,我想知道。

票数 5
EN

Stack Overflow用户

发布于 2009-06-12 05:10:15

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

但是,如果搜索模式匹配多行,则需要通知我们,因为逐行搜索将无法工作。

票数 2
EN

Stack Overflow用户

发布于 2009-06-12 06:37:59

声称FileChannel.map将把整个文件加载到内存中是错误的,引用了FileChannel.map()返回的MappedByteBuffer。它是一个“Direct缓冲区”,它不会耗尽您的内存(Direct缓冲区使用OS虚拟内存子系统根据需要对内存中的数据进行分页,允许处理大量内存,因为它们是物理RAM)。但是,再一次,一个MBB只对~2GB的文件起作用。

试试这个:

代码语言:javascript
复制
FileChannel fc = new FileInputStream(fFile).getChannel();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());

CharBuffer chrBuff = mbb.asCharBuffer();

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

不过,我不知道怎么处理解码。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/985076

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档