2016-07-27 32 views
1

Ich habe zwei einfache Beispieldateien mit der folgenden Datenstruktur:
person.csvJava IO: Speicher/Performance-Problem beim „letzten Zeilen“ von Dateien lesen mit BufferedReader

0|John 
1|Maria 
2|Anne 

und

Artikel .csv

0|car|blue 
0|bycicle|red 
1|phone|gold 
2|purse|black 
2|book|black 

ich brauche alle zugehörigen Linien zu sammeln (Linien mit der gleichen Identität, in diesem Beispiel der ganze Zahl 0, 1 oder 2) alle Dateien und Mach etwas (egal zu dieser Frage) mit ihnen, nachdem du sie gesammelt hast. Die erste Gruppe von verwandten Linien (Liste von Strings) sollte wie folgt aussehen:

0|John 
0|car|blue 
0|bycicle|red 

Die zweite Gruppe von verwandten Linien:

1|Maria 
1|phone|gold 

usw.

Die eigentlichen Dateien sind etwa 5 bis 10 GB pro Datei. Die Dateien werden in der ersten Spalte sortiert und die Datei mit der kleinsten ID wird zuerst zum Lesen geöffnet. Der Speicher ist ein einschränkender Faktor (die gesamte Datei kann nicht im Speicher gelesen werden). In diesem Sinne habe ich den folgenden Code geschrieben, der gut zu lesen scheint, wenn man die meisten Zeilen liest und gruppiert, wie ich will ... aber der letzte Teil (in meinem Code habe ich eine Anzahl von 250.000 Gruppen gesetzt) ​​dauert wesentlich länger und Speicher verwendet Spikes.

Haupt

public class Main { 

    private static int groupCount = 0; 
    private static int totalGroupCount = 0; 
    private static long start = 0; 
    private static int lineCount; 

    public static void main(String[] args) { 
     GroupedReader groupedReader = new GroupedReader(); 
     groupedReader.orderReadersOnSmallestId(); 
     long fullStart = System.currentTimeMillis(); 
     start = System.currentTimeMillis(); 
     lineCount = 0; 
     while (groupedReader.hasNext()) { 
      groupCount++; 
      List<String> relatedLines = groupedReader.readNextGroup(); 
      for (String line : relatedLines) { 
       lineCount++; 
      } 
      totalGroupCount++; 
      if (groupCount == 250_000) { 
       System.out.println("Building " + NumberFormat.getNumberInstance(Locale.US).format(groupCount) + " groups took " + (System.currentTimeMillis() - start)/1e3 + " sec"); 
       groupCount = 0; 
       start = System.currentTimeMillis(); 
      } 
     } 
     System.out.println("Building " + NumberFormat.getNumberInstance(Locale.US).format(groupCount) + " groups took " + (System.currentTimeMillis() - start)/1e3 + " sec"); 
     System.out.println(String.format("Building [ %s ] groups from [ %s ] lines took %s seconds", NumberFormat.getNumberInstance(Locale.US).format(totalGroupCount), NumberFormat.getNumberInstance(Locale.US).format(lineCount), (System.currentTimeMillis() - fullStart)/1e3)); 
     System.out.println("all done!"); 
    } 
} 

GroupedReader ... einige Methoden ommited

public class GroupedReader { 

    private static final String DELIMITER = "|"; 
    private static final String INPUT_DIR = "src/main/resources/"; 

    private boolean EndOfFile = true; 
    private List<BufferedReader> sortedReaders; 
    private TreeMap<Integer, List<String>> cachedLines; 
    private List<String> relatedLines; 
    private int previousIdentifier; 

    public boolean hasNext() { 
     return (sortedReaders.isEmpty()) ? false : true; 
    } 

    public List<String> readNextGroup() { 
     updateCache(); 
     EndOfFile = true; 
     for (int i = 0; i < sortedReaders.size(); i++) { 
      List<String> currentLines = new ArrayList<>(); 
      try { 
       BufferedReader br = sortedReaders.get(i); 
       for (String line; (line = br.readLine()) != null;) { 
        int firstDelimiterIndex = StringUtils.ordinalIndexOf(line, DELIMITER, 1); 
        int currentIdentifier = Integer.parseInt(line.substring(0, firstDelimiterIndex)); 
        if (previousIdentifier == -1) { 
         // first iteration 
         previousIdentifier = currentIdentifier; 
         relatedLines.add(i + DELIMITER + line); 
         continue; 
        } else if (currentIdentifier > previousIdentifier) { 
         // next identifier, so put the lines in the cache 
         currentLines.add(i + DELIMITER + line); 
         if (cachedLines.get(currentIdentifier) != null) { 
          List<String> local = cachedLines.get(currentIdentifier); 
          local.add(i + DELIMITER + line); 
         } else { 
          cachedLines.put(currentIdentifier, currentLines); 
         } 
         EndOfFile = false; 
         break; 
        } else { 
         // same identifier 
         relatedLines.add(i + DELIMITER + line); 
        } 
       } 
       if (EndOfFile) { 
        // is this close needed? 
        br.close(); 
        sortedReaders.remove(br); 
       } 
      } catch (NumberFormatException | IOException e) { 
       e.printStackTrace(); 
      } 
     } 
     if (cachedLines.isEmpty()) cachedLines = null; 
     return relatedLines; 
    } 

    private void updateCache() { 
     if (cachedLines != null) { 
      previousIdentifier = cachedLines.firstKey(); 
      relatedLines = cachedLines.get(cachedLines.firstKey()); 
      cachedLines.remove(cachedLines.firstKey()); 
     } else { 
      previousIdentifier = -1; 
      relatedLines = new ArrayList<>(); 
      cachedLines = new TreeMap<>(); 
      // root of all evil...? 
      System.gc(); 
     } 
    } 
} 

ich versucht habe, „Spielen“ um mit explizit schließen Leser und Garbage Collector aufrufen, aber ich kann‘ t Finde den tatsächlichen Fehler in dem Code, den ich geschrieben habe.

Frage:
Was die Verlangsamung der nahe dem Ende der Datei gelesen wird verursacht?

Einfache SJSO log:

Building 250,000 groups took 0.394 sec 
Building 250,000 groups took 0.261 sec 
Building 250,000 groups took 0.289 sec 
... 
Building 250,000 groups took 0.281 sec 
Building 250,000 groups took 0.314 sec 
Building 211,661 groups took 10.829 sec 
Building [ 9,961,661 ] groups from [ 31,991,125 ] lines took 21.016 seconds 
all done! 

Antwort

0

System.gc() ist eine Anforderung, aber nicht garantieren, dass GC stattfinden.

Wenn Sie einen schnellen Weg sehen möchten, wo Zeit verbracht wird, fügen Sie an mehr Stellen im Code mehr Protokollierung hinzu und reduzieren Sie den groupCount auf einen kleineren Wert, um eine bessere Zeitaufteilung zu erhalten (10000?).

Wenn Sie richtig profilieren und ein besseres Verständnis erhalten möchten, dann verwenden Sie die Tools, die mit dem JDK geliefert werden, entweder die ältere visualvm oder die neue mission control.

Beide können im Ordner bin in Ihrer JDK-Installation gefunden werden.

+0

Das 'System.gc()' war/ist ein bisschen experimenteller Code und ich weiß, dass ich versuchen sollte, es zu vermeiden.Ich werde die Profilierungsoptionen ausprobieren, aber ich kann Ihre Antwort nicht akzeptieren, da es sich nicht um eine direkte Antwort auf meine Frage handelt. –

+0

Wenn Sie eine konkrete Antwort benötigen, müssen Sie die vollständigen Java-Klassen und einen vollständigen Speicherauszug Ihrer Datendateien veröffentlichen. Wenn dies eine wahre Eigenart ist, kann es nicht gefunden werden mit dem, was zur Verfügung gestellt wurde. Viel Glück mit den Werkzeugen! – UserF40