簡體   English   中英

讀取大於300 MB的大文件時發生異常

[英]exception while Read very large file > 300 MB

我的任務是在READ&WRITE模式下打開一個大文件 ,我需要通過搜索起點和終點來搜索該文件中的某些文本部分 然后,我需要將文本的搜索區域寫入新文件,並從原始文件中刪除該部分。

以上過程我將做更多次。 因此,我認為對於這些過程,通過CharBuffer將文件加載到內存中將很容易,並且可以通過MATCHER類輕松進行搜索。 但是我在讀取時收到HeapSpace異常 ,即使我通過像下面的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兆的虛擬地址空間中的文件是mmap編了進去,雖然這堆外。 (請注意,將占用300 Meg的虛擬地址空間, 直到垃圾回收MappedByteBuffer為止 。請參見下文進行討論。用於map的JavaDoc會就此警告您。)接下來,您將創建一個由mmap ed文件支持的ByteBuffer 這應該很好,因為它只是mmap ed文件的“視圖”,因此應占用最少的額外內存。 這將是堆中的一個小對象,並帶有指向堆外一個大對象的“指針”。 接下來,將其解碼為CharBuffer ,這意味着您復制了300 MB的緩沖區,但是由於在char只有2個字節,所以您復制了600 MB(在堆上)。

為了回應評論,並查看JDK源代碼以確保在按OP那樣調用map() ,實際上確實將整個文件映射到內存中。 查看openJDK 6 b14 Windows本機代碼sun.nio.ch.FileChannelImpl.c ,它首先調用CreateFileMapping ,然后調用MapViewOfFile 查看此源代碼,如果您要求將整個文件映射到內存中,則此方法將完全按照您的要求進行。 引用MSDN:

映射文件將使文件的指定部分在調用進程的地址空間中可見。

對於大於地址空間的文件,一次只能映射一小部分文件數據。 當第一個視圖完成時,您可以取消映射並映射一個新視圖。

OP調用映射的方式,文件的“指定部分”是整個文件。 這不會導致耗盡,但是會導致虛擬地址空間耗盡,這仍然是OOM錯誤。 這可能會像耗盡堆一樣徹底殺死您的應用程序。

最后,當您創建MatcherMatcher可能會制作600 MB CharBuffer更多副本,具體取決於您如何使用它。 哎喲。 少數對象占用了很多內存! 給定一個Matcher每次調用toMatchResult() ,您都將創建整個 CharBufferString副本。 另外, 每次調用replaceAll() ,充其量只能創建整個CharBufferString副本。 最糟糕的是,您將創建一個StringBuffer ,然后將其緩慢擴展到replaceAll結果的完整大小(在堆上施加大量內存壓力),然后從中創建一個String

因此,如果您針對300 MB的文件在Matcher上調用replaceAll ,並且找到了匹配項,那么您將首先創建一系列更大的StringBuffer直到獲得600 MB的StringBuffer 然后,將創建此StringBufferString副本。 這可以快速輕松地導致堆耗盡。

這是最重要的一點: Matcher並未針對大型緩沖區進行優化。 您可以非常輕松地(無需計划)制作許多非常大的對象。 當執行與您正在執行的操作類似的操作並遇到內存耗盡時,然后查看Matcher的源代碼時,我發現了這一點。

注意:沒有unmap調用。 調用map ,由MappedByteBuffer綁住的堆外部的虛擬地址空間將停留在此處,直到對MappedByteBuffer進行垃圾回收為止。 如此一來,您將無法對文件執行某些操作(刪除,重命名,...),直到對MappedByteBuffer進行垃圾回收為止。 如果在不同文件上調用映射足夠的時間,但是在堆中沒有足夠的內存壓力來強制進行垃圾回收,則您可能會在堆外部耗盡內存。 有關討論,請參見Bug 4724038

由於所有上述討論的結果,如果你會用它來使Matcher上的大文件,你將使用replaceAllMatcher ,然后內存映射I / O可能不是要走的路。 它只會在堆上創建過多的大對象,並在堆外耗盡大量虛擬地址空間。 在32位Windows下,您只有2GB(或者如果更改了設置,則為3GB)用於JVM的虛擬地址空間,這將對堆內部和外部施加很大的內存壓力。

對於這個答案的冗長,我深表歉意,但我想透徹一點。 如果您認為以上任何部分是錯誤的,請發表評論並這樣說。 不會進行報復性的投票。 非常肯定上述所有內容都是正確的,但是如果出現問題,我想知道。

您的搜索模式是否匹配多行? 如果不是這樣,最簡單的解決方案是逐行讀取:)。 真的很簡單

但是,如果搜索模式匹配多行,則您需要告知我們,因為逐行搜索將無法進行。

使用緩沖區一次讀取大量文件有一個技巧:每次將新字符串讀入緩沖區時,請確保其長度為l,即子字符串的長度l = length(substring); 如果find(buffer,substring)返回TRUE,則開始(不是eof);
buffer [0..l] =子字符串; buffer [l + 1,end] = read_new_chars_intobuffer; 結束

關於FileChannel.map()返回的MappedByteBuffer的說法,聲稱FileChannel.map將整個文件加載到內存中是錯誤的。 它是一個“直接字節緩沖區”,不會耗盡您的內存(直接字節緩沖區使用OS虛擬內存子系統根據需要將數據分頁到內存中和從內存中分頁出來,從而使它們可以尋址更大的內存塊,因為它們是物理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的視圖 ,而不是副本。

不過,我不確定如何處理解碼。

就我而言,在類路徑之后添加-Djava.compiler=NONE可以解決此問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM