2015-04-15 10 views
6

Ich versuche, große Datei (ca. 516 MB) zu lesen, und es hat 18 Zeilen Text. Ich habe versucht, den Code selbst aufschreiben und bekam einen Fehler in der ersten Zeile des Codes beim Versuch, die Datei zu lesen:OutOfMemoryError: Java-Heap-Speicher beim Versuch, große Datei zu lesen

try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) { 
     String line; 
     while ((line = br.readLine()) != null) { 
      String fileContent = line; 
     } 
} 

Hinweis: Datei vorhanden ist, und seine Größe beträgt ca. 516mb. Wenn es eine andere sicherere und schnellere Methode zum Lesen gibt, sagen Sie mir bitte (auch wenn es Zeilenumbrüche gibt). Edit: Hier habe ich versucht, durch Scanner verwenden, aber es dauert etwas länger und gibt dann den gleichen Fehler

try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) { 
    Scanner scanner = new Scanner(br); 
    while(scanner.hasNext()){ 
     int index = Integer.parseInt(scanner.next()); 
     // and here do something with index 
    } 
} 

ich auch gespaltet Datei in 1800 Zeilen, bekam aber nichts feste

+1

Müssen Sie die gesamte Datei in den Speicher laden? – higuaro

+0

@higuaro ja. Ich möchte diese Datei – user3260312

+0

@higuaro sortieren oder gibt es eine Möglichkeit, diese Datei separat durch Schleifen zu lesen? – user3260312

Antwort

0

Erhöhen Sie die Heap-Größe witn -Xmx.

Für Ihre Datei würde ich vorschlagen, eine Einstellung von -Xmx1536m mindestens so eine Dateigröße von 516M wird beim Laden erhöhen. Intern verwendet Jaava 16 Bits, um ein Zeichen darzustellen, daher dauert eine Datei mit einem Text von 10 Bytes ca. 1 Sekunde. 20 Bytes wie String (außer bei Verwendung von UTF-8 mit vielen zusammengesetzten Zeichen).

+0

Wird es Probleme oder wird die Leistung meines Programms langsamer? – user3260312

+0

@ user3260312 Solange der Computer genügend Hauptspeicher hat, sollte es kein Problem mit der Erhöhung der Speichergröße sein. Wenn Sie nicht genügend Hauptspeicher haben, müssen Sie nach einer anderen Lösung suchen (unabhängig von Ihrer Programmiersprache). –

+0

Obwohl nicht direkt verwandt - sagen, dass Java intern 16 Bits verwendet, um ein Zeichen darzustellen, ist nicht ganz richtig. Java verwendet UTF-16 als Zeichencodierung für Unicode. und nicht alle Unicode-Zeichen können auf 16-Bit-Werte abgebildet werden, was bedeutet, dass es einige Zeichen gibt, die zwei 16-Bit-Code-Einheiten erfordern. – m3th0dman

4

Die Verwendung von BufferedReader hilft Ihnen bereits dabei, das Laden der gesamten Datei in den Speicher zu vermeiden. Also, für weitere Verbesserungen, wie Sie jede Zahl genannt wird durch ein Leerzeichen getrennt, so statt dessen:

line = br.readLine(); 

wir den Leser mit einem Scanner wickeln kann,

Scanner scanner = new Scanner(br); 

Und extrahieren jede Zahl in der Datei scanner.next(); verwenden und speichern Sie es in ein integer-Array wird auch helfen, die Speichernutzung zu reduzieren:

int val = Integer.parseInt(scanner.next()); 

Dies wird Ihnen helfen zu vermeiden den ganzen Satz zu lesen.

Und Sie können auch Ihre Puffergröße begrenzen für BufferedReader

BufferedReader br = new BufferedReader(new FileReader("test.txt") , 8*1024); 

Weitere Informationen Does the Scanner class load the entire file into memory at once?

+0

Die Standardgröße des Puffers im BufferedReader ist bereits 8192 Byte, also gibt es keinen Sinn setze es manuell auf diesen Wert. – RaphMclee

+0

Scanner arbeitet zu langsam für mich – user3260312

+0

@ user3260312 zu langsam? Ist dein Gedächtnisfehler verschwunden? Kannst du ein bisschen mehr beschreiben? :) –

0

Hinweis: Erhöhung der Heap-Speicher Grenze eine Datei mit 18 Zeilen zu sortieren, ist nur eine faule Art und Weise zu Lösen Sie ein Programmierproblem, diese Philosophie, immer das Gedächtnis zu erhöhen, anstatt das eigentliche Problem zu lösen, ist ein Grund für Java-Programme schlechter Ruhm über Langsamkeit und dergleichen.

Mein Ratschlag, den Speicher für eine solche Aufgabe nicht zu vergrößern, besteht darin, die Datei zeilenweise aufzuteilen und die Zeilen so zu verschmelzen, dass sie einem MergeSort ähneln. Auf diese Weise kann Ihr Programm vergrößert werden, wenn die Dateigröße zunimmt.

die Datei in mehrere „Zeilenunter Dateien“ So zu teilen, verwenden Sie die read Methode der BufferedReader Klasse:

private void splitBigFile() throws IOException { 
    // A 10 Mb buffer size is decent enough 
    final int BUFFER_SIZE = 1024 * 1024 * 10; 

    try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) { 
     String line; 

     int fileIndex = 0; 
     FileWriter currentSplitFile = new FileWriter(new File("test_split.txt." + fileIndex)); 

     char buffer[] = new char[BUFFER_SIZE]; 

     int readed = 0; 
     while ((readed = br.read(buffer)) != -1) { 
      // Inspect the buffer in search of the new line character 
      boolean endLineProcessed = false; 
      for (int i = 0; i < readed; i++) { 
       if (buffer[i] == '\n') { 
        // This chunk contains the new line character, write this last chunk the current file and create a new one 
        currentSplitFile.write(buffer, 0, i); 
        fileIndex++; 
        currentSplitFile = new FileWriter(new File("test_split.txt." + fileIndex)); 
        currentSplitFile.write(buffer, i, readed - i); 
        endLineProcessed = true; 
       } 
      } 

      // If not end of line found, just write the chunk 
      if (!endLineProcessed) { 
       currentSplitFile.write(buffer, 0, readed); 
      } 
     } 
    } 
} 

sie zu fusionieren, öffnen alle die Dateien und einen separaten Puffer halten (eine kleine eine, wie 2 mb) für jede von ihnen, lesen Sie den ersten Teil jeder Datei und dort haben Sie genug Informationen, um den Index der Dateien neu zu ordnen. Lesen Sie Chunks weiter, wenn einige der Dateien Verbindungen haben.

+2

"... ist ein Grund für Java-Programme schlechter Ruhm über Langsamkeit und dergleichen" - Was Sie sagen, ist wahr, aber ist nicht nur auf Java-Programme beschränkt ... leider. –

+0

Auch diese Lösung hat ihre Begrenzung als Datei mit 516m und nur 18 Zeilen ist riesig und so haben selbst die geteilten Dateien eine vernünftige Größe ... –

+0

Es macht nichts, wenn die geteilten Dateien nicht so klein sind, sobald Zeilen sie getrennt sind kann mit kleinen Puffern eingerichtet werden, ohne dass eine der Dateien vollständig im Speicher geladen wird, und die Lösung kann für weitere Zeilen skaliert werden. IMHO ist dies noch mehr Speicher effizienter, dass die Heap zu erhöhen, um die ganze Datei – higuaro

0

Es ist schwer zu erraten, ohne ein Speicherprofil Ihrer Anwendung, Ihre JVM-Einstellungen und Hardware zu verstehen. Es könnte so einfach sein, nur JVM-Speichereinstellungen zu ändern oder so hart wie mit RandomFileAccess zu gehen und Bytes selbst zu konvertieren. Ich werde es hier versuchen. Das Problem kann nur mit der Tatsache zu tun, dass Sie versuchen, sehr lange Zeilen zu lesen, nicht mit der Tatsache, dass die Datei groß ist.

Wenn Sie bei der Umsetzung von BufferedReader.readLine aussehen() finden Sie so etwas wie diese (vereinfachte Version) finden Sie unter:

String readLine() { 
    StringBuffer sb = new StringBuffer(defaultStringBufferCapacity); 
    while (true) { 
    if (endOfLine) return sb.toString(); 
    fillInternalBufferAndAdvancePointers(defaultCharBufferCapacity);//(*) 
    sb.append(internalBuffer); //(**) 
    } 
} 
// defaultStringBufferCapacity = 80, can't be changed 
// defaultCharBufferCapacity = 8*1024, can be altered 

(*) ist die kritischste Linie hier. Es versucht, den internen Puffer der begrenzten Größe 8K zu füllen und den Zeichenpuffer an StringBuffer anzufügen. 516 MB Datei mit 18 Zeilen bedeutet, dass jede Zeile ~ 28 MB im Speicher belegt. So versucht es, 8K-Array ~ 3500 mal pro Zeile zuzuteilen und zu kopieren.

(**) Dann versucht es, dieses Array in StringBuffer der Standardkapazität 80. Dies verursacht zusätzliche Zuweisungen für StringBuffer, um sicherzustellen, dass der interne Puffer groß genug ist, um die Zeichenfolge ~ 25 zusätzliche Zuweisungen pro Zeile zu halten, wenn ich bin nicht falsch.

Also im Grunde würde ich empfehlen Größe des internen Puffers auf 1 MB zu erhöhen, geben Sie einfach zusätzliche Parameter zu der Instanz von BufferedReader wie:

new BufferedReader(..., 1024*1024); 
+0

den gleichen Fehler zu laden. – user3260312

0

EDIT Es ist das gleiche für Java Heap-Speicher ist , deklariere Variablen innerhalb oder außerhalb der Schleife.

Nur ein Ratschlag.

Wenn Sie können, sollten Sie keine Variablen innerhalb der Schleifen deklarieren, deshalb können Sie den Java-Heap-Speicher füllen. In diesem Beispiel wäre es, wenn es möglich wäre, besser:

try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) { 
     String line; 
     String fileContent; 
     while ((line = br.readLine()) != null) { 
      fileContent = line; 
     } 
} 

Warum? Weil Java in jeder Iteration neuen Speicherplatz im Heap für die gleiche Variable reserviert (Java erwägt eine neue, andere Variable (vielleicht möchten Sie das, aber wahrscheinlich nicht)) und wenn die Schleife groß genug ist, kann der Heap voll sein.

+0

Nicht wirklich, diese Variablen werden jedes Mal freigegeben, wenn die While-Schleife einen Zyklus durchlaufen hat, also wird der GC sie löschen. Und der Compiler optimiert dies wahrscheinlich schon. – RaphMclee

+0

Ok, danke @RaphMclee Ich habe festgestellt, dass der GC sie nur entfernt, wenn die Schleife vorbei ist. Danke für die Information. – maiklahoz

+0

Danke für die Bearbeitung, und platzieren Sie es zuerst. – greybeard

1

Java wurde entwickelt, um mit großen Datenmengen zu arbeiten, die größer sind als der verfügbare Speicher. Auf der Lover-Level-API-Datei ist ein Stream, möglicherweise endlos.

Aber mit Chip-Speicher Menschen bevorzugen einfachen Weg - lesen Sie alle in den Speicher und arbeiten mit Speicher. Normalerweise funktioniert es, aber nicht in Ihrem Fall. Das Erhöhen des Speichers verbirgt dieses Problem nur, bis Sie eine größere Datei haben. Also, es ist Zeit, es richtig zu machen.

Ich kenne Ihren Sortieransatz nicht, was Sie zum Vergleich verwenden. Wenn es gut ist, dann kann es einen sortierbaren Schlüssel oder Index für jeden String erzeugen. Sie lesen die Datei einmal, erstellen eine Karte mit solchen Schlüsseln, sortieren sie und erstellen dann eine sortierte Datei basierend auf dieser sortierten Karte. Das wäre (Worst-Case-Szenario) in Ihrem Fall 1 + 18 Datei Lesungen plus 1 Schreiben.

Wenn Sie jedoch keinen solchen Schlüssel haben und Zeichenfolgen Zeichen für Zeichen vergleichen, müssen Sie zwei Eingabeströme haben und diese miteinander vergleichen. Wenn eine Zeichenfolge nicht an der richtigen Stelle ist, schreiben Sie die Datei in der richtigen Reihenfolge neu und wiederholen Sie den Vorgang. Worst-Case-Szenario 18 * 18 Lesungen zum Vergleichen, 18 * 2 Lesen zum Schreiben und 18 Schriften.

Das ist die Konsequenz für eine solche Architektur, wenn Sie Ihre Daten in riesigen Strings in riesigen Dateien aufbewahren.

Verwandte Themen