2017-09-24 2 views
0

Unten ist eine Klasse, die ich eine ConcurrentMap<String, List<String>> in eine Datei schreibt. Der Schlüssel in der Map ist der Pfad, und der Wert in der Map muss sequenziell in die Datei geschrieben werden. Diese Task<Void> wird jedes Mal gibt es 1.000 Werte in der Karte genannt:Java mehrere Öffnen und Schließen von Dateien zum Schreiben

public class MapWriter extends Task<Void> { 

private final ParsingProducerConsumerContext context; 

public MapWriter(ParsingProducerConsumerContext context) { 
    this.context = context; 
} 

@Override 
protected Void call() throws Exception { 
    if (!isCancelled() || !context.isEmpty()) { 
     ConcurrentMap<String, List<String>> jsonObjectMap = context.fetchAndReset(); 

     jsonObjectMap.entrySet().forEach((t) -> {     
      try { 
       FileUtils.writeLines(new File(context.getPath() + t.getKey() + "\\sorted.json"), t.getValue(), true); 
      } catch (IOException ex) { 
       context.getLogger().log("Error writing to disk:"); 
       context.getLogger().log(ex.toString()); 
       context.stopEverything(); 
      } 
     }); 

     context.getLogger().log(jsonObjectMap.values().stream().mapToInt(List::size).sum() + " schedules written to disk "); 
    } else { 
     context.getLogger().log("Nothing to write"); 
    } 

    return null; 
} 
} 

Während der ganzen Zeit diese Aufgabe ausgeführt wird, gibt es ein Produzent Task eine ~ 2GByte Datei Zeile für Zeile zu lesen, die von einem Verbraucher verarbeitet wird und platziert in ConcurrentMap<String, List<String>>.

Während dies funktioniert, ist es sehr langsam!

Meine Forschungsergebnisse legen nahe, dass beim wiederholten Öffnen und Schließen von Dateien ein erheblicher Overhead besteht, um die Leistung zu beeinträchtigen, und fragte sich, ob der folgende Ansatz besser sein könnte?

Pflegen Sie eine Map<String, File> von File Objekte, die offen sind. Wenn der Schlüssel in ConcurrentMap<String, List<String>> einer geöffneten Datei entspricht, verwenden Sie diese File Referenz zum Schreiben Wenn alle Verarbeitung abgeschlossen ist, durchlaufen Sie über Map<String, File> Werte und schließen Sie jede Datei.

Klingt das sinnvoll? Es wären jedoch ca. 100 Dateien geöffnet.

EDIT :: Ich habe einen einfachen Benchmark mit System.nanoTime(). Die Datei, die Zeile für Zeile vom Hersteller importiert wird, beträgt ca. 2 GB, und jede Zeile liegt zwischen 6 KB und 10 KB (in der List<String>).

Außerdem ist ein OutOfMemory-Fehler aufgetreten! Ich nehme an, weil die 2GByte effektiv in den Speicher geladen werden und nicht schnell genug ausgeschrieben werden?

514 jsonObjects written to disk in 2258007ms 538 jsonObjects written to disk in 2525166ms 1372 jsonObjects written to disk in 169959ms 1690 jsonObjects written to disk in 720824ms 9079 jsonObjects written to disk in 5221168ms 22552 jsonObjects written to disk in 6943207ms 13392 jsonObjects written to disk in 6475639ms 0 jsonObjects written to disk in 6ms 0 jsonObjects written to disk in 5ms 0 jsonObjects written to disk in 5ms 40 jsonObjects written to disk in 23108ms 631 jsonObjects written to disk in 200269ms 3883 jsonObjects written to disk in 2054177ms Producer failed with java.lang.OutOfMemoryError: GC overhead limit exceeded

Für Vollständigkeit, hier ist der Produzent Klasse:

public class NRODJsonProducer extends Task<Void> { 

private final ParsingProducerConsumerContext context; 

public NRODJsonProducer(ParsingProducerConsumerContext context) { 
    this.context = context; 
} 

@Override 
protected Void call() throws Exception { 
    context.getLogger().log("Producer created"); 

    LineIterator li = FileUtils.lineIterator(new File(context.getPath() + context.getFilterFile())); 

    while (li.hasNext()) { 
     try { 
      context.getQueue().put(li.next()); 
     } catch (InterruptedException ex) { 
      Logger.getLogger(NRODJsonProducer.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 

    LineIterator.closeQuietly(li); 

    context.getLogger().log("Producer finished..."); 

    return null; 
} 

}

+0

Geben Sie es uns und lassen Sie es uns wissen? –

Antwort

0

Ich sehe nicht, warum. Dieser Code schreibt alles für einen Schlüssel in eine Datei mit demselben Namen und geht dann zum nächsten Schlüssel über. Wenn der Produzent einen anderen Eintrag für diesen Schlüssel erzeugt, überschreibt er den vorherigen Eintrag, und dieser Code schreibt die Datei erneut. Das Öffnen von Dateien hilft nicht.

Das eigentliche Problem scheint zu sein, dass Sie die gleichen Daten in die Datei schreiben, weil Sie nie einen verarbeiteten Schlüssel aus der Karte entfernen.

Hinweis: Ihr Util-Zustand ist falsch. Es sollte sein

if (!isCancelled() && !context.isEmpty()) 
+0

Ah ... wenn 'context.fetchAndReset()' aufgerufen wird, wird die Map aus dem Kontext abgerufen. Die Map in dem Kontext wird durch eine neue ersetzt, so dass sie MapWriter immer hat, sie wird nur gelesen und dann verworfen. – swshaun

Verwandte Themen