2012-08-30 45 views
15

我有一個包含2000萬行文字的大文本文件。當我使用下面的程序讀取文件時,它工作得很好,事實上,我可以讀取更大的文件而不會出現內存問題。我的Java程序讀取大文本文件內存不足,任何人都可以解釋爲什麼嗎?

public static void main(String[] args) throws IOException { 
    File tempFile = new File("temp.dat"); 
    String tempLine = null; 
    BufferedReader br = null; 
    int lineCount = 0; 
    try { 
     br = new BufferedReader(new FileReader(tempFile)); 
     while ((tempLine = br.readLine()) != null) { 
      lineCount += 1; 
     } 
    } catch (Exception e) { 
     System.out.println("br error: " +e.getMessage()); 
    } finally { 
     br.close(); 
     System.out.println(lineCount + " lines read from file"); 
    } 
} 

但是,如果我需要閱讀它之前,一些記錄到這個文件追加的BufferedReader中消耗了大量的內存(我剛使用Windows任務管理器來監視這個,不是很科學,但我知道它演示了這個問題)。修改後的程序如下,與第一個相同,除了我先將單個記錄追加到文件中。

public static void main(String[] args) throws IOException { 
    File tempFile = new File("temp.dat"); 
    PrintWriter pw = null; 
    try { 
     pw = new PrintWriter(new BufferedWriter(new FileWriter(tempFile, true))); 
     pw.println(" "); 
    } catch (Exception e) { 
     System.out.println("pw error: " + e.getMessage()); 
    } finally { 
     pw.close(); 
    } 

    String tempLine = null; 
    BufferedReader br = null; 
    int lineCount = 0; 
    try { 
     br = new BufferedReader(new FileReader(tempFile)); 
     while ((tempLine = br.readLine()) != null) { 
      lineCount += 1; 
     } 
    } catch (Exception e) { 
     System.out.println("br error: " +e.getMessage()); 
    } finally { 
     br.close(); 
     System.out.println(lineCount + " lines read from file"); 
    } 
} 

Windows任務管理器,在當前行的大的凸起顯示了內存消耗,當我運行該程序的第二個版本的屏幕截圖。

task manager screenshot

所以我能夠讀取該文件,但不運行內存不足。但是我擁有超過5000萬條記錄的更大文件,當我對他們運行該程序時遇到內存不足異常?有人可以解釋爲什麼程序的第一個版本適用於任何大小的文件,但第二個程序的行爲如此不同並以失敗告終?我在Windows 7上運行有:

Java版本 「1.7.0_05」
的Java(TM)SE運行時環境(建立1.7.0_05-B05)
的HotSpot的Java(TM)客戶端虛擬機(建設23.1-B03 ,混合模式,共享)

+1

這是不是'BufferedReader'這需要所有的記憶一些很好的分析運行的虛擬機獲得按堆轉儲?我寧願懷疑它會是這樣做的'FileWriter'。 –

+1

是否有將BufferedWriter添加到組合中的理由?如果你使用'新的PrintWriter(new FileWriter(...))',你還會遇到同樣的問題嗎? –

+2

(與問題無關,但我必須指出你可以在finally塊中得到一個NPE。處理這個問題的方法是使用Java SE 7的try-with-resource或Java SE 6使用單獨嘗試的最後和捕捉,並避免使用空值。) –

回答

-3

你需要用更大的堆來啓動java。嘗試-Xmx1024m作爲java命令的參數。

基本上你會需要比文件大小更多的內存。

+6

你能解釋我爲什麼需要對第二個節目來說是一個更大的堆,但不是第一個?該程序的第一個版本工作得很好,並使用非常小的堆大小。 BufferedReader一次處理文件1行,所以它根本不需要太多內存? –

+0

我同意tony_h。 –

0

每次執行下面的Java程序的Java中,要創建一個全新的對象:

tempLine = br.readLine() 

每次調用的readLine(時間我相信)它可能產生其留在一個新的String對象每次調用重新分配時將堆賦值給tempLine。

因此,由於GC並不總是被稱爲數以千計的對象可以在幾秒鐘內留在堆上。

有人說,每1000行左右調用一次System.gc()是個壞主意,但如果能解決你的問題,我會好奇的。此外,你可以在每行之後運行此命令,基本標記每個對象當作垃圾收集:

tempLine=null; 
+0

我不認爲這是問題所在。當我運行該程序的只讀版本時,BufferedReader工作得很好,根本沒有內存問題。這個問題只發生在我讀取文件之前,用一個使用printwriter在文件中附加一行的部分。 –

+0

你的行數是多少?另外,如果你使用JDK 1.6.0_22或更高版本,我相信你會得到一個多線程垃圾回收器,我很好奇你會得到什麼樣的行爲?另外,BufferedWriter不允許你增加緩衝區大小嗎?另一種方法:嘗試使用InputStreamReader和FileInputStream讀取數據並將其存儲在char中,然後使用FileOutputStream編寫該char。 – djangofan

0
 pw = new PrintWriter(new BufferedWriter(new FileWriter(tempFile, true))); 

你儘量不使用的BufferedWriter?如果你追加幾行到最後也許你不需要緩衝區?如果你這樣做,考慮使用一個字節數組(集合或字符串生成器)。最後你是否在java 1.6_32中嘗試了相同的方法?可能是作家之一的新版本中的錯誤。

可以在pw.close()之前和之後打印空閒內存; ?

System.out.println("before wr close :" + Runtime.getRuntime().freeMemory()); 

和之後類似​​的接近和讀者密切

0

這可能是因爲你可能不會是具有換行符在文件/回車可言了。在這種情況下,readLine()會嘗試從您的文件中創建一個可能用完存儲器的單個字符串。的readLine()的

Java文檔:

Reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), a carriage return ('\r'), or a carriage return followed immediately by a linefeed.

+0

不幸的是,這些文件都是正確描述的,並且我在解析文件時得到了正確的行數。 –

0

你試過:

A)創建一個新的文件實例使用的閱讀,但指向同一個文件。 B)在第二部分中讀取完全不同的文件。

我想知道是否File對象仍然以某種方式附加到PrintWriter,或者如果操作系統正在做一些有趣的文件句柄。這些測試應該告訴你在哪裏關注。

這看起來不是代碼的問題,你認爲它不應該中斷的邏輯看起來很合理,所以它必須是一些基礎功能。

+0

謝謝@Glen Lamb,我認爲你的建議很有意義。然而,我已經在這個問題上花費了太多時間,最後決定以另一種方式完全避免這個問題。如果我有時間回到它,我會發布我得到的任何結果。 –

1

可以啓動一個Java虛擬機與VM-Options

-XX:+HeapDumpOnOutOfMemoryError 

這會寫一個堆轉儲到一個文件,它可以用於查找泄漏嫌疑人

使用要分析的「+」來添加選項和' - '刪除選項。

如果您使用的是Eclipse Java內存分析器插件MAT從與泄漏嫌疑人等

相關問題