2GB große CSV-Datei einlesen, sortieren & schreiben

Hallo zusammen :slight_smile:

Entschuldigt zuerst, das ich mein Thema so in „Allgemeine Themen“ knalle. Ich wusste nicht wohin damit :smiley:

Aber zum Problem:
Ich habe eine 2GB große CSV Datei, was in meinem Fall so 30Mio ungeordnete Datensätze sind.
Ich würde die Datensätze gerne nach Datum (Bsp. 20150403) und Uhrzeit (Bsp. 023104) ordnen :slight_smile:
Bisher habe ich sie in eingelesen und in einer ArrayList abgelegt → Ging so ne halbe Stunde
Dann hab ich die Liste sortiert → Ging ruck zuck :slight_smile:
Aber das schreiben würde nun hochgerechnet ca. 5 Tage dauern :confused:

Hat jemand vielleicht irgendeine Idee wie ich das schneller schreiben kann oder auch gerne einen anderen Ansatz zur Lösung :slight_smile:
Vielen Danke schon im voraus :slight_smile:

Etwas Code wäre an dieser Stelle sicherlich angebracht, eventuell liegt es ja nur an einer Kleinigkeit, die dem ein oder anderen sofort ins Auge springt.

klar, ganz einfach: schneller schreiben und schneller lesen!

für mehr Details müsste man Code sehen…

Mal ein Schuss ins Blaue: Verwendest Du BufferedReader-/Writer? Falls nicht, mach das. Die Klasse https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html bietet Dir Hilfsmethoden, um das recht bequem zu machen.

Ganz ehrlich, geh über eine Datenbank - H2 oder so reicht aus. Du kannst dann sequentiell hineinschreiben und lesen, und auch ganz leicht sortieren, wie du willst. CSV hat so einige Tücken (wenn du keine Kontrolle über die Erzeugung hast), da würde ich OpenCSV empfehlen.

@Landei
sprichst du auf dann Nutzung von Export-Optionen wie
Export data from H2 database into CSV - Stack Overflow
an?

das Sortieren ist ja offensichtlich die geringste Schwierigkeit,
falls Einlesen und Schreiben selber zu machen wäre (“Du kannst dann sequentiell hineinschreiben und lesen”), wäre nichts gewonnen

und auch noch in der Liste der Mutmach-Posts: 2 GB zu lesen oder zu schreiben sollte jeweils nicht mehr als 1 Min. dauern, muss nur richtig gemacht werden

das klingt nach Verbesserungswürdig

was heisst hochgerechnet ? Weisst du dass es so lange dauert, vermutest du es ? Woran hochgerechnet ?

Entschuldigt bitte, hier der Code.

while (null != (zeile = FileReader.readLine())) {
				if (ersteZeile == false) {

					System.out.println(zeile);
					String[] split = zeile.split(";");

					zeit = split[2] + " " + split[3];

					sdfSource = new SimpleDateFormat("yyyyMMdd HHmmss");
					date = sdfSource.parse(zeit);
					millisec = date.getTime();
					datensatzList.add(new Datensatz(millisec, zeile));
				}
				ersteZeile = false;
			}
			FileReader.close();

			System.out.println("Sortieren");
			Collections.sort(datensatzList);

			System.out.println("Schreiben");
			BufferedWriter bw = new BufferedWriter(new FileWriter(
					"alldata_new.csv"));

			for (Datensatz ds : datensatzList) {
				bw.write(ds.getLine());
				bw.flush();
			}

			bw.close();

Ich bin mir nicht sicher aber eventuell könnte es ein wenig helfen das bw.flush() direkt vor das close zu machen.
Könnte mir jedenfalls vorstellen, dass das etwsa bringen könnte

Das Mantra für bessere Performanz: “Am schnellesten ist das, was man nicht tut…”
Das trifft ganz besonders auf schleifen zu.

Also:
Das Überspringen der ersten Zeile machst Du ab besten, in dem Du vor der Schleife ein mal FileReader.readLine() aufrufts, dann fällt das if in der Schleife weg.

Außerdem kannst Du Zeile 10 auch vor die Schleife ziehen. Lieber ein mal ein Objekt um sonst erzeugen, als Millionen mal.

Das sollte das Lesen deutlich beschleunigen.

Zum Schreiben fällt mich nichts ein.

[quote=Clayn]Ich bin mir nicht sicher aber eventuell könnte es ein wenig helfen dasbw.flush()direkt vor das close zu machen.[/quote]Du hast recht, dass ist die Schreibbremse.

bye
TT

Danke. Werd ich gleich umsetzen

*** Edit ***

[QUOTE=Clayn]Ich bin mir nicht sicher aber eventuell könnte es ein wenig helfen das bw.flush() direkt vor das close zu machen.
Könnte mir jedenfalls vorstellen, dass das etwsa bringen könnte[/QUOTE]

Hab ich schon probiert, aber macht es leider auch nicht ersichtlich schneller :confused:

und wenn Du statt bw.write() bw.writeln() verwendest? (mal davon abgesehen dass mit der ersten Methode alle Zeilenumbrüche verschwinden…)

An sonsten wäre noch zu klären, was ds.getLine() macht…

bye
TT

immer etwas Vorsicht bei Erzeugung von Ressourcen wie SimpleDateFormat in Schleifen,
muss nicht millionenfach angelegt werden, 1x reicht,

flush() besser auch aus der Schleife raus, ja,
ein BufferedWriter soll ja gerade erst mehrere Schreibkommandos sammeln, nur bei größeren gesammelten Mengen selten schreiben


wie sieht es eigentlich bei kleineren Dateigrößen aus, bei 1 Mio. Datensätze Schreiben 5 Tage/ 30 ~4 Stunden?

zum Vergleich auf die Schnelle ein (vollständiges!) Testprogramm, welches 6 Mio. einfache Einträge generiert und in 300 MB-Datei schreibt, in 17 sec bei mir,
davon das Schreiben nur etwa 1 sec, bin selber überrascht, meine alte Daumenregel ist 100 MB/sec für Lesen/ Schreiben/ Kopieren

SimpleDateFormat in die Generierungsschleife macht es bei mir deutlich langsamer, 3fache Zeit zum Generieren,
flush in die Schleife ~verfünfzehnfacht das Schreiben in meinem Beispiel

die Daten zu 300 MB CSV reichen schon, bei mir 1 GB an Arbeitsspeicher zu füllen, und mehr kann ich gerade nicht zusammenkonfigurieren…

public class Test2
{

    public static void main(String[] args)
        throws Exception
    {
        SimpleDateFormat sdfSource = new SimpleDateFormat("yyyyMMdd HHmmss");
        List<Datensatz> datensatzList = new ArrayList<>();

        final int n = 6000000;
        final int m = n/10;

        long j = 0;
        long start = System.currentTimeMillis();
        for (int i = 0; i < n; i++)
        {
            Date date = new Date();
            String line = sdfSource.format(date) + ";halli hallo;mittellange Zeile;" + i;
            datensatzList.add(new Datensatz(line));
            if (j++ > m)
            {
                System.out.println("generiert: " + (System.currentTimeMillis() - start) + " - 10%");
                j = 0;
            }
        }
        System.out.println("fertig generiert: " + (System.currentTimeMillis() - start));

        System.out.println("Schreiben");
        File f = new File("alldata_new.csv");
        BufferedWriter bw = new BufferedWriter(new FileWriter(f));

        j = 0;
        for (Datensatz ds : datensatzList)
        {
            bw.write(ds.getLine());
            bw.write("
");
            if (j++ > m)
            {
                System.out.println("schreiben: " + (System.currentTimeMillis() - start) + " - 10%");
                j = 0;
            }
        }

        bw.flush();
        bw.close();
        System.out.println("fertig schreiben: " + (System.currentTimeMillis() - start));
        System.out.println("Dateigröße: " + f.length());
    }
}


class Datensatz
{
    String line;


    public Datensatz(String line)
    {
        this.line = line;
    }

    public String getLine()
    {
        return this.line;
    }
}

Vielen Dank. Ich hab die Änderungen gemacht. Nach nicht mal 2 Min war es durch :smiley: Dankeeee

Es lag in dem Fall an des SimpleDateFormat Objekten :confused: Also den 30Mio die ich aus Jux erzeugt habe

Viele Grüße

Zeile 5 mit System.out.println(zeile); ist auch ganz böse.
Beim Einlesen jede Zeile auf System.out auszugeben ist sehr sehr teuer.

Das SimpleDateFormat einmalig zu initialisieren wurde ja bereits genannt.

Dennoch ist das Parsen und umwandeln in ein Date-Objekt auch mit einigem Aufwand verbunden.
Dazu kommt noch das du nun eine neue Datenstruktur Datensatz benötigst, die ebenfalls Speicher beansprucht.

Den einfachste Weg, da Datum und Uhrzeit ja schon strukturiert und als String sortierbar sind, diese an den Anfang einer Zeile zu hängen.

liste.add(split[2] + split[3] + zeile);

Danach sortieren als ganz normalen String.

liste.sort(String.CASE_INSENSITIVE_ORDER);

Zum Ausgeben der Daten würde ich einen PrintWriter verwenden und dabei die Anfangs hinzugefügten Zeitwerte entfernen.

out.println(item.substring(14));

Zusammengefasst so:

try(FileReader fileReader = new FileReader(inputFilename)) {
  fileReader.readLine(); //Erste Zeile lesen
  while (null != (zeile = fileReader.readLine())) {
    String[] split = zeile.split(";");
    list.add(split[2] +  split[3] + zeile);
  }
} catch (FileNotFoundException ex) {
  ex.printStackTrace();
}
 
System.out.println("Sortieren");
Collections.sort(String.CASE_INSENSITIVE_ORDER);
 
System.out.println("Schreiben");
try(PrintWriter out = new PrintWriter(outputFilename)) {
  for (String item : list) {
    out.println(item.substring(14));
  }
} catch (FileNotFoundException ex) {
  ex.printStackTrace();
}```

Und das Programm mit ordentlich Heap laufen lassen;)

[QUOTE=SlaterB]@Landei
sprichst du auf dann Nutzung von Export-Optionen wie
Export data from H2 database into CSV - Stack Overflow
an?[/QUOTE]

Habe nur vermutet, dass es da sowas geben sollte (wobei es auch nicht schwer selbst zu schreiben ist). Aber das Thema hat sich ja erledigt.