[英]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錯誤。 這可能會像耗盡堆一樣徹底殺死您的應用程序。
最后,當您創建Matcher
, Matcher
可能會制作600 MB CharBuffer
更多副本,具體取決於您如何使用它。 哎喲。 少數對象占用了很多內存! 給定一個Matcher
, 每次調用toMatchResult()
,您都將創建整個 CharBuffer
的String
副本。 另外, 每次調用replaceAll()
,充其量只能創建整個CharBuffer
的String
副本。 最糟糕的是,您將創建一個StringBuffer
,然后將其緩慢擴展到replaceAll
結果的完整大小(在堆上施加大量內存壓力),然后從中創建一個String
。
因此,如果您針對300 MB的文件在Matcher
上調用replaceAll
,並且找到了匹配項,那么您將首先創建一系列更大的StringBuffer
直到獲得600 MB的StringBuffer
。 然后,將創建此StringBuffer
的String
副本。 這可以快速輕松地導致堆耗盡。
這是最重要的一點: Matcher
並未針對大型緩沖區進行優化。 您可以非常輕松地(無需計划)制作許多非常大的對象。 當執行與您正在執行的操作類似的操作並遇到內存耗盡時,然后查看Matcher
的源代碼時,我發現了這一點。
注意:沒有unmap
調用。 調用map
,由MappedByteBuffer
綁住的堆外部的虛擬地址空間將停留在此處,直到對MappedByteBuffer
進行垃圾回收為止。 如此一來,您將無法對文件執行某些操作(刪除,重命名,...),直到對MappedByteBuffer
進行垃圾回收為止。 如果在不同文件上調用映射足夠的時間,但是在堆中沒有足夠的內存壓力來強制進行垃圾回收,則您可能會在堆外部耗盡內存。 有關討論,請參見Bug 4724038 。
由於所有上述討論的結果,如果你會用它來使Matcher
上的大文件,你將使用replaceAll
的Matcher
,然后內存映射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.