Wie kopiere ich große Datendateien Zeile für Zeile?

9

Ich habe eine 35 GB CSVDatei. Ich möchte jede Zeile lesen und die Zeile in eine neue CSV schreiben, wenn sie einer Bedingung entspricht.

try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("source.csv"))) {
    try (BufferedReader br = Files.newBufferedReader(Paths.get("target.csv"))) {
        br.lines().parallel()
            .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
            .forEach(line -> {
                writer.write(line + "\n");
        });
    }
}

Dies dauert ca. 7 Minuten. Ist es möglich, diesen Prozess noch weiter zu beschleunigen?

Mitgliedssound
quelle
1
Ja, Sie könnten versuchen, dies nicht von Java aus zu tun, sondern direkt von Linux / Windows / etc. Betriebssystem. Java wird interpretiert und es wird immer einen Overhead bei der Verwendung geben. Abgesehen davon, nein, ich habe keine offensichtliche Möglichkeit, es zu beschleunigen, und 7 Minuten für 35 GB scheinen mir vernünftig.
Tim Biegeleisen
1
Vielleicht parallelmacht das Entfernen das schneller? Und mischt das nicht die Zeilen herum?
Thilo
1
Erstellen Sie sich BufferedWriterselbst mit dem Konstruktor , mit dem Sie die Puffergröße festlegen können. Möglicherweise macht eine größere (oder kleinere) Puffergröße einen Unterschied. Ich würde versuchen, die BufferedWriterPuffergröße an die Puffergröße des Host-Betriebssystems anzupassen.
Abra
5
@ TimBiegeleisen: "Java wird interpretiert" ist bestenfalls irreführend und fast immer auch falsch. Ja, für einige Optimierungen müssen Sie möglicherweise die JVM-Welt verlassen, aber dies in Java schneller zu tun, ist definitiv machbar.
Joachim Sauer
1
Sie sollten die Anwendung profilieren, um festzustellen, ob es Hotspots gibt, gegen die Sie etwas unternehmen können. Sie werden nicht viel gegen das rohe E / A tun können (der Standard-8192-Byte-Puffer ist nicht so schlecht, da es sich um Sektorgrößen usw. handelt), aber möglicherweise passieren (intern) Dinge, die Sie möglicherweise können arbeiten mit.
Kayaman

Antworten:

4

Wenn dies eine Option ist, können Sie GZipInputStream / GZipOutputStream verwenden, um die Festplatten-E / A zu minimieren.

Files.newBufferedReader / Writer verwenden eine Standardpuffergröße von 8 KB, glaube ich. Sie könnten einen größeren Puffer versuchen.

Die Konvertierung in String, Unicode, verlangsamt sich auf (und verwendet den doppelten Speicher). Das verwendete UTF-8 ist nicht so einfach wie StandardCharsets.ISO_8859_1.

Am besten wäre es, wenn Sie größtenteils mit Bytes arbeiten können und diese nur für bestimmte CSV-Felder in String konvertieren.

Eine Speicherzuordnungsdatei ist möglicherweise am besten geeignet. Parallelität kann von Dateibereichen verwendet werden, die die Datei aufspucken.

try (FileChannel sourceChannel = new RandomAccessFile("source.csv","r").getChannel(); ...
MappedByteBuffer buf = sourceChannel.map(...);

Dies wird ein bisschen viel Code, der die Zeilen richtig einfügt (byte)'\n', aber nicht übermäßig komplex ist.

Joop Eggen
quelle
Das Problem beim Lesen von Bytes ist, dass ich in der realen Welt den Zeilenanfang und die Teilzeichenfolge eines bestimmten Zeichens auswerten und nur den verbleibenden Teil der Zeile in die Outfile schreiben muss. Also kann ich die Zeilen wahrscheinlich nicht nur als Bytes lesen?
Membersound
Ich habe gerade den GZipInputStream + GZipOutputStreamvollständigen Speicher auf einer Ramdisk getestet . Die Leistung war viel schlechter ...
Membersound
1
Auf Gzip: dann ist es keine langsame Festplatte. Ja, Bytes sind eine Option: Zeilenumbrüche, Kommas, Tabulatoren und Semikolons können alle als Bytes behandelt werden und sind erheblich schneller als als Zeichenfolgen. Bytes als UTF-8 bis UTF-16 Zeichen bis String bis UTF-8 bis Bytes.
Joop Eggen
1
Ordnen Sie einfach verschiedene Teile der Datei im Laufe der Zeit zu. Wenn Sie das Limit erreicht haben, erstellen Sie einfach eine neue MappedByteBufferPosition aus der letzten Position, die als gut bekannt ist ( FileChannel.mapdauert lange).
Joachim Sauer
1
Im Jahr 2019 besteht keine Notwendigkeit zu verwenden new RandomAccessFile(…).getChannel(). Einfach benutzen FileChannel.open(…).
Holger
0

Sie können dies versuchen:

try (BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile), 1024 * 1024 * 64)) {
  try (BufferedReader br = new BufferedReader(new FileReader(sourceFile), 1024 * 1024 * 64)) {

Ich denke, das spart Ihnen ein oder zwei Minuten. Der Test kann auf meinem Computer in ca. 4 Minuten durchgeführt werden, indem die Puffergröße angegeben wird.

könnte es schneller sein Versuche dies:

final char[] cbuf = new char[1024 * 1024 * 128];

try (Writer writer = new FileWriter(targetFile)) {
  try (Reader br = new FileReader(sourceFile)) {
    int cnt = 0;
    while ((cnt = br.read(cbuf)) > 0) {
      // add your code to process/split the buffer into lines.
      writer.write(cbuf, 0, cnt);
    }
  }
}

Dies sollte Ihnen drei oder vier Minuten sparen.

Wenn das noch nicht reicht. (Der Grund, warum Sie die Frage wahrscheinlich stellen, ist wahrscheinlich, dass Sie die Aufgabe wiederholt ausführen müssen.) wenn Sie es in einer Minute oder sogar ein paar Sekunden erledigen möchten. Dann sollten Sie die Daten verarbeiten und in db speichern und dann die Aufgabe von mehreren Servern verarbeiten.

user_3380739
quelle
Zu Ihrem letzten Beispiel: Wie kann ich dann den cbufInhalt bewerten und nur Teile ausschreiben? Und müsste ich den Puffer zurücksetzen, sobald er voll ist? (
Woher
0

Dank all Ihrer Vorschläge war der schnellste Austausch mit dem Autor, mit BufferedOutputStreamdem sich ca. 25% verbessert haben:

   try (BufferedReader reader = Files.newBufferedReader(Paths.get("sample.csv"))) {
        try (BufferedOutputStream writer = new BufferedOutputStream(Files.newOutputStream(Paths.get("target.csv")), 1024 * 16)) {
            reader.lines().parallel()
                    .filter(line -> StringUtils.isNotBlank(line)) //bit more complex in real world
                    .forEach(line -> {
                        writer.write((line + "\n").getBytes());
                    });
        }
    }

Trotzdem ist die Leistung BufferedReaderbesser als BufferedInputStreamin meinem Fall.

Mitgliedssound
quelle