2009-05-21 2 views
11

Ich lese in einer Textdatei mit FileInputStream, die den Inhalt der Datei in ein Byte-Array legt. Ich konvertiere dann das Byte-Array mit einem neuen String (Byte) in einen String.Textdateianalyse in Java

Sobald ich die Zeichenfolge habe ich String.split("\n") verwenden, um die Datei in ein String-Array zu teilen und dann das String-Array und Parsing es durch eine String.split(",") und halten Sie den Inhalt in einem Arraylist.

Ich habe eine 200MB + Datei und es läuft nicht mehr genug Speicher, wenn ich die JVM mit einem 1GB Speicher starten. Ich weiß, dass ich irgendwo etwas richtig machen muss, ich bin mir nur nicht sicher, ob die Art, wie ich analysiere, falsch ist oder die Datenstruktur, die ich verwende.

Es dauert auch ungefähr 12 Sekunden, um die Datei zu analysieren, die wie viel Zeit scheint. Kann jemand darauf hinweisen, was ich tue, was dazu führt, dass mir der Speicher ausgeht und was dazu führen könnte, dass mein Programm langsam läuft?

Der Inhalt der Datei sehen wie folgt:

"12334", "100", "1.233", "TEST", "TEXT", "1234" 
"12334", "100", "1.233", "TEST", "TEXT", "1234" 
. 
. 
. 
"12334", "100", "1.233", "TEST", "TEXT", "1234" 

Dank

+0

Bitte klarstellen: Sagen Sie, dass Sie -Xmx1024m verwenden, um Ihre JVM zu starten, und Sie immer noch einen OutOfMemoryError erhalten? – duffymo

+0

Mit einem kompilierten Muster wäre besser (sogar rollen Sie Ihre eigene fachliche Aufteilung). Aber es sieht so aus, als ob Sie nur eine Menge Objektdaten haben. –

+0

Duffymo, das ist genau der Fall. Ich setze die JVM mit der -Xmx1024m und bekomme OutOfMemoryError –

Antwort

8

Es klingt wie Sie etwas falsch mir antust - eine ganze Schöpfung lotta Objekt geht.

Wie repräsentativ ist diese "Test" -Datei? Was machst du wirklich mit diesen Daten? Wenn das typisch für das ist, was Sie wirklich haben, würde ich sagen, dass es viele Wiederholungen in diesen Daten gibt.

Wenn alles in Strings ist, starten Sie mit einem BufferedReader, um jede Zeile zu lesen. Ordnen Sie diese Liste auf eine Größe zu, die nahe an dem ist, was Sie benötigen, damit Sie nicht jedes Mal Ressourcen verschwenden, wenn Sie sie hinzufügen. Teilen Sie jede dieser Zeilen am Komma; Achten Sie darauf, die Anführungszeichen zu streichen.

Sie könnten sich fragen: "Warum brauche ich diese ganze Datei auf einmal im Speicher?" Kannst du ein wenig lesen, ein bisschen verarbeiten und hast das Ganze nie in Erinnerung? Nur Sie kennen Ihr Problem gut genug, um zu antworten.

Vielleicht können Sie jvisualvm starten, wenn Sie JDK 6 haben und sehen, was mit dem Speicher passiert. Das wäre ein großer Hinweis.

+0

Die Art und Weise, wie der Fragesteller es tut, scheint ein großes char [] (in einem String) und dann Strings zu erzeugen, die Slices davon sind, was überraschenderweise tatsächlich der überspeichernde effiziente Weg ist, dies zu tun. (Nicht überprüfte Implementierung von Split. Natürlich ist es alle Implementierung abhängig.) –

+0

Sie sind auf "Uber effizient", Tom. Mein Rat würde es tatsächlich noch schlimmer machen. Wenn das Problem weiter besteht, denke ich, dass es im laufenden Betrieb und jvisualvm am meisten hilft. – duffymo

+0

Nun, da wir Streams mit Java 8 haben, frage ich mich, ob dies mit funktionaler Programmierung effizienter gemacht werden kann. Dafür wurden Ströme geboren. – duffymo

2

Wenn Sie über 200.000.000 Zeichendateien verfügen und diese alle fünf Zeichen teilen, haben Sie 40.000.000 String Objekte. Angenommen, sie teilen tatsächliche Zeichendaten mit den ursprünglichen 400 MB String (char ist 2 Bytes). Ein String ist sagen 32 Bytes, so dass es 1.280.000.000 Bytes String Objekte ist.

(es ist wahrscheinlich erwähnenswert, dass diese abhängig sehr Implementierung ist. split mit völlig neuen Träger völlig Saiten schaffen könnte char[] oder OTOH, einige gemeinsame String Werte teilen. Einige Java-Implementierungen nicht das Aufschneiden von char[] zu verwenden. Einige nutzen können eine UTF-8-artige kompakte Form und ergeben sehr schlechte Random Access Zeiten.)

Auch bei längeren Strings sind das eine Menge Objekte. Bei so vielen Daten möchten Sie wahrscheinlich mit den meisten Daten in kompakter Form wie das Original arbeiten (nur mit Indizes). Konvertieren Sie nur zu Objekten, die Sie benötigen. Die Implementierung sollte datenbankartig sein (obwohl sie Strings variabler Länge traditionell nicht effizient handhaben).

4

Es klingt, als ob Sie derzeit 3 ​​Kopien der gesamten Datei im Speicher haben: das Byte-Array, die Zeichenfolge und das Array der Zeilen.

Anstatt die Bytes in ein Bytearray zu lesen und dann unter Verwendung von new String() in Zeichen umzurechnen, wäre es besser, einen InputStreamReader zu verwenden, der schrittweise in Zeichen konvertiert wird, anstatt alle nach vorne.

Anstatt String.split ("\ n") zu verwenden, um die einzelnen Zeilen zu erhalten, sollten Sie eine Zeile nach der anderen lesen. Sie können die readLine() Methode in BufferedReader verwenden.

versuchen, etwas wie folgt aus:

BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8")); 
try { 
    while (true) { 
    String line = reader.readLine(); 
    if (line == null) break; 
    String[] fields = line.split(","); 
    // process fields here 
    } 
} finally { 
    reader.close(); 
} 
+0

Die ursprüngliche Art und Weise die Strings (sollten) alle die gleiche Unterstützung char [] teilen, und daher effizienter sein. Eine Zeilenaufteilung ist wahrscheinlich nicht so schlimm, da pro Zeile nur ein Zeichen [] vorhanden ist. –

+0

(Und das Bytearray muss nicht gleichzeitig mit dem Zeilenarray im Speicher sein.) –

+0

Ich hatte das Gefühl, dass ich viele Kopien des Dateiinhalts im Speicher hatte. Ich werde das ausprobieren und den Unterschied sehen –

11

Ich bin nicht sicher, wie effizient es Speicher-weise ist, aber mein erster Ansatz wäre eine Scanner verwenden, da es unglaublich einfach zu bedienen:

File file = new File("/path/to/my/file.txt"); 
Scanner input = new Scanner(file); 

while(input.hasNext()) { 
    String nextToken = input.next(); 
    //or to process line by line 
    String nextLine = input.nextLine(); 
} 

input.close(); 

Überprüfen Sie die API, um das Trennzeichen zu ändern, das zum Teilen von Token verwendet wird.

5

Schauen Sie sich diese Seiten an. Sie enthalten viele Open-Source-CSV-Parser. JSaPar ist einer von ihnen.

+0

Irgendein bestimmter Vorschlag? –

+0

Nun, ich bin ein wenig voreingenommen, da ich der Autor der JSaPar-Bibliothek bin. Deshalb habe ich es in meiner Antwort erwähnt, aber eine der anderen Bibliotheken ist möglicherweise besser für Sie geeignet, je nachdem, welches Problem Sie lösen möchten. – stenix

0

Während des Anrufs/Aufrufen Ihres Programms Sie diesen Befehl verwenden können: java [-Optionen] classname [args ...]
anstelle von [-Optionen] bieten Sie mehr Speicher zB -Xmx1024m oder mehr. Aber das ist nur ein Workaround, Sie müssen Ihren Parsing-Mechanismus ändern.