簡體   English   中英

嘗試從大文本文件讀取/寫入時出現OutOfMemoryError

[英]OutOfMemoryError when trying to read/write from a huge text file

我正在嘗試讀/寫一個巨大的文本文件。 但是當我嘗試這樣做時,我得到錯誤:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.lang.AbstractStringBuilder.expandCapacity(Unknown Source)
    at java.lang.AbstractStringBuilder.append(Unknown Source)
    at java.lang.StringBuilder.append(Unknown Source)
    at ReadWriteTextFile.getContents(ReadWriteTextFile.java:52)
    at ReadWriteTextFile.main(ReadWriteTextFile.java:148)

我的代碼如下:

import java.io.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class ReadWriteTextFile {

  /**
  * Fetch the entire contents of a text file, and return it in a String.
  * This style of implementation does not throw Exceptions to the caller.
  *
  * @param aFile is a file which already exists and can be read.
  */    
  static public String getContents(File aFile) {
    //...checks on aFile are elided
      StringBuilder contents = new StringBuilder(); 
      int maxlines = 1000; //counts max lines t read/write to the file
      BufferedReader input = null;
      BufferedWriter bw = null;

    try {
      //use buffering, reading one line at a time
      //FileReader always assumes default encoding is OK!
      input =  new BufferedReader(new FileReader(aFile));
      try {
          String line = null; //not declared within while loop
        /*
        * readLine is a bit quirky :
        * it returns the content of a line MINUS the newline.
        * it returns null only for the END of the stream.
        * it returns an empty String if two newlines appear in a row.
        */
        //for (int i = 0; i < 100; i++){
        //int count = 0;//initiates the line counter
      while (( line = input.readLine()) != null){

          int count = 0;//initiates the line counter    
          String modified1 = line.substring(2,17);
          String modified2 = line.substring(18,33);
          String modified3 = line.substring(40);        
          String result = "empty";
          result = modified1 + ",," +modified2 + modified3;
          System.out.println (result);          

//        contents.append(line);
//        contents.append(System.getProperty("line.separator"));
          //int count = 0;//initiates the line counter
          try {

              contents.append(line);
              contents.append(System.getProperty("line.separator"));
          String content = result;

          File file = new File("C:\\temp\\out.txt");//output path

          // if file doesnt exists, then create it
          if (!file.exists()) {
          file.createNewFile();
          }
          for ( int i = 0; i < 1000; i++){
              if (count++ % maxlines == 0) {
          FileWriter fw = new FileWriter(file.getAbsoluteFile(),true);
          bw = new BufferedWriter(fw);      
              bw.write(content);
          bw.newLine(); 
          }
          bw.close();
          }

        } catch (IOException e) {
            e.printStackTrace();
        }

        //}
        }
      }
      finally {
          input.close();
          bw.close();

      }
    }
    catch (IOException ex){
        ex.printStackTrace();
    }

    return contents.toString();
  }


/**
  * Change the contents of text file in its entirety, overwriting any
  * existing text.
  *
  * This style of implementation throws all exceptions to the caller.
  *
  * @param aFile is an existing file which can be written to.
  * @throws IllegalArgumentException if param does not comply.
  * @throws FileNotFoundException if the file does not exist.
  * @throws IOException if problem encountered during write.
  */
  static public void setContents(File aFile, String aContents)
                                 throws FileNotFoundException, IOException {
    if (aFile == null) {
        throw new IllegalArgumentException("File should not be null.");
    }
    if (!aFile.exists()) {
        throw new FileNotFoundException ("File does not exist: " + aFile);
    }
    if (!aFile.isFile()) {
        throw new IllegalArgumentException("Should not be a directory: " + aFile);
    }
    if (!aFile.canWrite()) {
        throw new IllegalArgumentException("File cannot be written: " + aFile);
    }

    //use buffering
    Writer output = new BufferedWriter(new FileWriter(aFile, true));
    try {
      //FileWriter always assumes default encoding is OK!
        output.write( aContents );
    }
    finally {
      output.close();
    }

  }

  /** Simple test harness.   */
  public static void main (String... aArguments) throws IOException {
      File testFile = new File("C:\\temp\\in.txt");//input path
      System.out.println("\n" + getContents(testFile));

  }

}

我試圖添加一個計數器(計數),以便在讀取一定量的行后刷新緩沖區。 它沒用。 我知道計數器不能正常工作。 在執行特殊數量的“while”循環后,它不會變為零。 我在while循環之前和之后添加了一個“for”循環以清空計數器,但這樣做也沒有用。

有什么建議嗎?

嘗試使用FileInputStream而不是BufferedReader / Writer。 當我使用FileInputStream時,我可以復制一個超過36百萬行的虛擬日志文件,並且在不到幾秒的時間內就會有近500MB的大小。

FileInputStream in = new FileInputStream(from); //Read data from a file
FileOutputStream out = new FileOutputStream(to); //Write data to a file
byte[] buffer = new byte[4096]; //Buffer size, Usually 1024-4096
int len;
while ((len = in.read(buffer, 0, buffer.length)) > 0) {
    out.write(buffer, 0, len);
}
//Close the FileStreams
in.close();
out.close();

如果你想逐行讀取文件而不是字節塊,你可以使用BufferedReader,但方式不同。

// Removed redundant exists()/createNewFile() calls altogether
String line;
BufferedReader br = new BufferedReader(new FileReader(aFile));
BufferedWriter output = new BufferedWriter(new FileWriter(file, true));
while ((line = br.readLine()) != null) {
      String modified1 = line.substring(2,17);
      String modified2 = line.substring(18,33);
      String modified3 = line.substring(40); 
      String result = "empty";
      result = modified1 + ",," +modified2 + modified3;
      System.out.println (result);
      output.append(result + "\n");//Use \r\n for Windows EOL
}
//Close Streams
br.close();
output.close();

就像EJP所說的那樣,不要將整個文件讀入內存 - 這根本不是一件好事。 您最好的選擇是逐個讀取每一行或一次讀取文件的塊 - 但是,為了准確,逐行讀取它可能是最好的。

while ((line = br.readLine()) != null) ,您應該執行在此處加載的整個文件所需的內容,同時只將1行加載到內存中。 (例如檢查一行是否包含_或從中獲取文本)。

您可以嘗試避免OOM異常的另一件事是使用多個字符串。

if(contents.length() => (Integer.MAX_VALUE-5000)) { //-5000 to give some headway when checking
    . . .
}

不要嘗試將大文件讀入內存。 他們不合適。 找到一種方法,一次處理一行文件,一次處理一個記錄,或一次處理一個塊。 我在這里看不出任何理由你為什么不能這樣做。

在構建圍繞同一FileFileWriter之前立即調用File.exists()File.createNewFile()完全是浪費時間。

我試圖添加一個計數器(計數),以便在讀取一定量的行后刷新緩沖區。 它沒用。 我知道計數器不能正常工作。 在執行特殊數量的“while”循環后,它不會變為零。 我在while循環之前和之后添加了一個“for”循環以清空計數器,但這樣做也沒有用。

有什么建議嗎?

內存不足錯誤是因為您的文件非常龐大,導致該文件的所有內容無法讀入函數getContents(File aFile)中的本地變量contents

刷新緩沖區與它無關。 使用PrintWriter而不是BufferedWriter可能有助於清理代碼。 通過使用PrintWriter,您不必執行以下操作:

bw.write(content);
bw.newLine(); 

您可以將其更改為:

printWriter.println(content);

您也忘了告訴我們您的用例。 最后,您所做的只是打印文件的所有內容。 你可以逐行完成這個。

要讀取Java中的大文件,您應該使用java.util.scanner或apache commons LineIterator。 這兩種方法都不會將整個文件加載到內存中並逐行讀取文件。 能夠使用LineIterator讀取大小超過1GB的文件。 有關更多詳細信息,請訪問此鏈接http://www.baeldung.com/java-read-lines-large-file和示例。

暫無
暫無

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

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