2016-05-03 6 views
0

In meinem Projekt müssen wir eine sehr große Datei lesen, in der jede Zeile durch ein Sonderzeichen ("|") getrennt gekennzeichnet ist. Leider kann ich keine Parallelität verwenden, da es notwendig ist, zwischen dem letzten Zeichen einer Zeile und dem ersten Zeichen der nächsten Zeile eine Validierung durchzuführen, um zu entscheiden, ob sie extrahiert wird oder nicht. Wie auch immer, die Anforderung ist sehr einfach: Teile die Linie in Token, analysiere sie und speichere nur einige von ihnen im Speicher. Der Code ist sehr einfach, etwas wie unten:String.split() temporäre Objekte und Garbage Collect

Aber dieses kleine Stück Code ist sehr, sehr ineffizient. Die Methode split() erzeugt zu viele temporäre Objekte, die nicht gesammelt werden (wie am besten hier erklärt. http://chrononsystems.com/blog/hidden-evils-of-javas-stringsplit-and-stringr

Zum Vergleich: eine 5 MB-Datei wurde um 35 MB Speicher am Ende der Datei Prozess mit

.

getestet habe ich einige Alternativen wie:

Aber keiner von ihnen scheint effizient genug zu sein. Mit Hilfe von JProfiler konnte ich feststellen, dass die Speicherkapazität temporärer Objekte zu hoch ist (35 mb verwendet, aber nur 15 mb werden tatsächlich von gültigen Objekten verwendet).

Dann entscheide ich mich für einen einfachen Test: nach 50.000 Zeilen lesen, explizite Aufruf von System.gc(). Am Ende des Prozesses ist die Speicherbelegung von 35 MB auf 16 MB gesunken. Ich habe viele, viele Male getestet und immer das gleiche Ergebnis bekommen.

Ich weiß aufrufen, dass der Aufruf von System.gc() ist eine schlechte Praxis (wie in Why is it bad practice to call System.gc()? angezeigt). Aber gibt es in einem Cenario eine andere Alternative, bei der die Methode split() millionenfach aufgerufen werden kann?

[UPDATE] Ich benutze einen 5 mb nur für Testzwecke Datei, aber das System soll, ist hier viel größere Dateien (500 MB ~ 1 Gb)

+2

* "Die Methode split() generiert zu viele temporäre Objekte, die nicht gesammelt wurden (wie am besten hier erklärt: http://chrononsystems.com/blog/hidden-evils-of-javas-stringsplit-and-stringr). "Schade, es erklärt nicht, was Sie behaupten. Es ist auch nicht klar, warum Sie Ihre Zeichenfolge aufteilen möchten, anstatt sie zu analysieren." – Tom

+0

Was ist das Kriterium, nach dem Sie die Elemente von 'Token' akzeptieren oder ablehnen? –

+3

Eine andere Lösung besteht darin, die Zeichenfolge nicht zu teilen, sondern die Zeichenfolge in-situ zu scannen/analysieren/zu verarbeiten. –

Antwort

1

Die erste und wichtigste Sache zu sagen verarbeiten, nicht mach dir Sorgen darüber. Die JVM verbraucht 35MB RAM, weil ihre Konfiguration sagt, dass das eine niedrige Menge ist. Wenn der hocheffiziente GC-Algorithmus entscheidet, dass es an der Zeit ist, werden alle diese Objekte entfernt, kein Problem.

Wenn Sie wirklich wollen, können Sie Java mit Speicherverwaltung Optionen aufrufen (z. B. java -Xmxn=...) - Ich schlage vor, es ist nicht wert, wenn Sie auf sehr eingeschränkter Hardware ausgeführt werden.

Wenn Sie jedoch wirklich vermeiden möchten, jedes Mal, wenn Sie eine Zeile verarbeiten, ein Array von String zuzuordnen, gibt es viele Möglichkeiten, dies zu tun.

Eine Möglichkeit ist, ein StringTokenizer zu verwenden:

StringTokenizer st = new StringTokenizer(line,"|"); 

    while (st.hasMoreElements()) { 
     process(st.nextElement()); 
    } 

Sie auch eine Linie vermeiden könnte zu einem zeitaufwendig. Rufen Sie Ihre Datei als Stream ab, verwenden Sie eine StreamTokenizer und verbrauchen Sie auf diese Weise jeweils ein Token.

die API-Dokumentation lesen für Scanner, BufferedInputStream, Reader - es gibt viele Möglichkeiten in diesem Bereich sind, weil Sie etwas Grundsätzliches tun.

Keine von diesen jedoch wird Java früher oder aggressiver GC verursachen. Wenn sich die JRE nicht zu wenig Speicher vornimmt, sammelt sie keinen Müll.

Versuche so etwas wie diese schreiben:

public static void main(String[] args) { 
    Random r = new Random(); 
    Integer x; 
    while(true) { 
     x = Integer.valueof(r.nextInt()); 
    } 
} 

Run es und sehen Sie Ihre JVM-Heap-Größe, wie es läuft (Put einen Schlaf, wenn die Nutzung zu schnell schießt, um zu sehen). Jedes Mal in der Schleife erstellt Java ein sogenanntes temporäres Objekt vom Typ Integer. Alle diese bleiben in dem Haufen, bis der GC entscheidet, dass er sie wegräumen muss. Du wirst sehen, dass es das nicht tun wird, bis es ein bestimmtes Level erreicht. Aber wenn es dieses Niveau erreicht, wird es einen guten Job machen, sicherzustellen, dass seine Grenzen nie überschritten werden.

1

Sie sollten Ihre Art der Analyse von Situationen anpassen. Während der Artikel über die Regex-Zusammenstellung unter der Haube im Allgemeinen korrekt ist, trifft er hier nicht zu. Wenn Sie sich die source code of String.split(String) ansehen, werden Sie sehen, dass sie nur an String.split(String,int) delegiert, die einen speziellen Code-Pfad für Muster hat, die aus nur einem Literal-Zeichen bestehen, einschließlich entdeckter Zeichen wie \|.

Das einzige temporäre Objekt, das in diesem Codepfad erstellt wird, ist ArrayList. Das Regex-Paket ist überhaupt nicht beteiligt; Diese Tatsache könnte Ihnen helfen zu verstehen, warum die Vorkompilierung eines Regex-Musters die Leistung hier nicht verbessert hat.

Wenn Sie einen Profiler verwenden, um zu dem Schluss zu kommen, dass zu viele Objekte vorhanden sind, sollten Sie ihn auch verwenden, um herauszufinden, welche Arten von Objekten vorhanden sind und woher sie kommen.

Aber es ist nicht klar, warum Sie sich überhaupt beschweren. Sie können die JVM so konfigurieren, dass sie einen bestimmten maximalen Speicher verwendet. Solange dieses Maximum nicht erreicht wurde, macht die JVM nur das, was Sie ihr gesagt haben, und verwendet diesen Speicher, anstatt CPU-Zyklen zu verschwenden, nur um den verfügbaren Speicher nicht zu nutzen. Wo liegt der Sinn darin, den verfügbaren Speicher nicht zu nutzen?

+0

Danke für Ihre Antwort ** Holger **, aber tatsächlich, wenn ich die Liste der Objekte im Profiler überprüfen, gibt es eine Menge von Objekt [] genau nach jedem Split() Anruf wächst. Ich versuche, mit kleinen Dateien zu testen, weil es schnell ist (ich kann 20 Tests durchführen und die durchschnittliche Zeit und den Speicherverbrauch ermitteln). Eine andere Sache, die interessant ist: in diesem Fall weiß ich, dass 32mb genug sein sollte, um die Datei zu verarbeiten, aber wenn ich den Test mit -Xms16m -Xmx32m führe, führt es zu "GC Overhead Limit überschritten" – DanielSP

+0

'ArrayList' kapselt ein' Object [] 'Beispiel, das ist nicht überraschend. Wenn der Fehler "GC overhead limit exceeded" überschritten wird, beweist dies, dass der Versuch, den Speicher unnötig zu beschränken, die Leistung beeinträchtigt, da dieser Fehler genau besagt, dass zu viel Zeit in die Garbage Collection investiert wurde, siehe http://stackoverflow.com/q/ 1393486/2711488 – Holger