Optimierung eines Programms: Mergen der Dateien

Adriano10

Bekanntes Mitglied
In einem Folder hab 100 000 und davon wollte 50 000 Dateien zu einer Datein zusammenfügen. Das Code funktioniert mit weniger Mengen, aber mit 50 000 dauert ewig, wie könnte ich mein Code optiemeren. Jede Vorschläge würde mich sehr freuen.

Java:
package com.company;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.io.*;

public class XMLReadWriter {
    public Document merge(String expression,
                                  File... files) throws Exception {
        XPathFactory xPathFactory = XPathFactory.newInstance();
        XPath xpath = xPathFactory.newXPath();
        XPathExpression compiledExpression = xpath
                .compile(expression);
        return merge(compiledExpression, files);
    }

    public Document merge(XPathExpression expression,
                                  File... files) throws Exception {
        DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
                .newInstance();
        docBuilderFactory
                .setIgnoringElementContentWhitespace(true);
        DocumentBuilder docBuilder = docBuilderFactory
                .newDocumentBuilder();
        Document base = docBuilder.parse(files[0]);

        Node results = (Node) expression.evaluate(base,
                XPathConstants.NODE);
        if (results == null) {
            throw new IOException(files[0]
                    + ": Fehlschlag" + files.length);
        }

        for (int i = 1; i < files.length; i++) {
            Document merge = docBuilder.parse(files[i]);
            Node nextResults = (Node) expression.evaluate(merge,
                    XPathConstants.NODE);
            while (nextResults.hasChildNodes()) {
                Node kid = nextResults.getFirstChild();
                nextResults.removeChild(kid);
                kid = base.importNode(kid, true);
                results.appendChild(kid);
            }
        }

        return base;
    }

    public static void print(Document doc) throws Exception {
        TransformerFactory transformerFactory = TransformerFactory
                .newInstance();
        Transformer transformer = transformerFactory
                .newTransformer();
        DOMSource source = new DOMSource(doc);
        Result result = new StreamResult(System.out);
        transformer.transform(source, result);
    }


    public static void write(Document doc) throws TransformerConfigurationException {
        TransformerFactory tFactory = TransformerFactory.newInstance();
        Transformer transformer = tFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        DOMSource source = new DOMSource(doc);
        StreamResult result = new StreamResult(new StringWriter());
        try {
            transformer.transform(source, result);
            Writer output = new BufferedWriter(new FileWriter("C:/Users/p05865/IdeaProjects/XMLDateien/Materials1Bis50000.xml"));
            String xmlOutput = result.getWriter().toString();
            output.write(xmlOutput);
            output.close();
            System.out.println("Ready");
        } catch (TransformerException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public File[] findAllFilesInFolder(File... folder) {
    
        return folder;
    }
}
Java:
package com.company;

import org.w3c.dom.Document;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Main {

    static String path = "C:/Users/p05865/IdeaProjects/muster/Material_1200668137_08092021_104636594.xml";

    public static void main(String[] args) throws Exception {

        // proper error/exception handling omitted for brevity
        com.company.XMLReadWriter xmlReadWriter = new com.company.XMLReadWriter();

        File file = new File(path);
        File[] files1 = new File[50000];
        File[] files = file.listFiles();
        int count = 0;
        for (int j = 0; j < files.length - 50000; j++){
                files1[count++] = files[j];

        }
            Document doc = xmlReadWriter.merge("/STEP-ProductInformation/Products", files1);
            xmlReadWriter.write(doc);
            xmlReadWriter.print(doc);


    }
}
 
Was bedeutet denn "ewig"? Wenn es pro Datei eine Zehntel Sekunde dauert, sollte es bei 50.000 Dateien 5.000 Sekunden dauern - das sind weniger als zwei Stunden. Und so oft wirst du doch keine 100.000 Dateien vorliegen haben.
Ja das kann fast bei 2 Stunden liegen, aber vielleicht kennt jemand anderen besseren Weg, so dass ich einige For Shleife ausweichen kann, jede Schleife kostet Zeit.
 
Da wird einfach der für die JVM verfügbare Speicher nicht ausreichen. Wurde dieser schon mal heraufgesetzt? Wie sind die Startparameter für dein Programm?
 
Hallo,

das Aneinanderfügen von Dateien sollte sehr schnell gehen. Dein Problem scheint zu sein, dass du xml-Dateien zunächst parst und dann wieder schreibst - das kostet natürlich Zeit (EDIT: Ich muss gestehen, dass ich deinen Code dafür nicht wirklich angesehen habe).
Ich kenne die Struktur deiner Dateien nicht; aber möglicherweise reicht es ja aus, die ersten und letzten paar Zeilen zu löschen und alles andere nur zu kopieren.

Gruß
Fritz
 
Da wird einfach der für die JVM verfügbare Speicher nicht ausreichen. Wurde dieser schon mal heraufgesetzt? Wie sind die Startparameter für dein Programm?
Am Ende, nach Zwei Stunden Warten wurde Fehlermeldung ausgegeben. Da das mich sehr verärgert, hab die Meldung leider nicht geachtet… wenn das daran liegt, gibt es den Ausweg?

Ich verstehe leider nicht, was sie unter Startparameter meinen. Das Code hab oben eingefügt…
 
Hallo,

das Aneinanderfügen von Dateien sollte sehr schnell gehen. Dein Problem scheint zu sein, dass du xml-Dateien zunächst parst und dann wieder schreibst - das kostet natürlich Zeit (EDIT: Ich muss gestehen, dass ich deinen Code dafür nicht wirklich angesehen habe).
Ich kenne die Struktur deiner Dateien nicht; aber möglicherweise reicht es ja aus, die ersten und letzten paar Zeilen zu löschen und alles andere nur zu kopieren.

Gruß
Fritz
Danke Fritz für die Rückmeldung… ha ich parse erst dann wird die Dateien miteinander gemergt, am ende wird die zusammengefügte Datei geschrieben
 
Danke Fritz für die Rückmeldung… ha ich parse erst dann wird die Dateien miteinander gemergt, am ende wird die zusammengefügte Datei geschrieben
Wenn deine Dateien etwa so aussehen:
Code:
<?xml version="1.0" encoding="utf-8"?>
<Bestellungen>
  <Bestellung>
    # irgendwas
  </Bestellung>
  <Bestellung>
    # irgendwas
  </Bestellung>
</Bestellungen>
dann kanst du in deine Ausgabedatei die beiden ersten und die letzte Zeile schreiben und sie ansonsten löschen.
Dann benötigst du keinen Parser und auch nicht mehr Arbeitsspeicher.
 
Wenn deine Dateien etwa so aussehen:
Code:
<?xml version="1.0" encoding="utf-8"?>
<Bestellungen>
  <Bestellung>
    # irgendwas
  </Bestellung>
  <Bestellung>
    # irgendwas
  </Bestellung>
</Bestellungen>
dann kanst du in deine Ausgabedatei die beiden ersten und die letzte Zeile schreiben und sie ansonsten löschen.
Dann benötigst du keinen Parser und auch nicht mehr Arbeitsspeicher.
Naja er sucht ja einen bestimmten XPath in jeder Datei und übernimmt die Treffer in die Zieldatei. Weiterhin ist es riskant anzunehmen, dass wirklich alle Quelldateien formatiert vorliegen.
 
In der print-Methode sehe ich keinen Sinn. Es wird ein Transformer erzeugt aber keine XSL übergeben. Das Ziel-XML wird komplett ein x-tes mal in den Speicher geladen um es dann auf System.out wieder rauszuhauen. Das ist sinnlos!
Warum wird hier: StreamResult result = new StreamResult(new StringWriter()); erst mal alles in eine String geschrieben, anstatt sofort den FileWriter/BufferedWriter zu verwenden?
 
In der print-Methode sehe ich keinen Sinn. Es wird ein Transformer erzeugt aber keine XSL übergeben. Das Ziel-XML wird komplett ein x-tes mal in den Speicher geladen um es dann auf System.out wieder rauszuhauen. Das ist sinnlos!
Warum wird hier: StreamResult result = new StreamResult(new StringWriter()); erst mal alles in eine String geschrieben, anstatt sofort den FileWriter/BufferedWriter zu verwenden?
Stimmt. diese Methode hab nur am Anfang gebraucht, zu testen. jetzt ist ohne Funktion.
 
Ich würde so ein Problem eher wie von @fhoffmann vorgeschlagen angehen. Wenn da noch XML verarbeitet werden muss, dann würde sich bei den Mengen ggf. StAX lohnen. Die Ausgabedatei würde ich nur einmal öffnen, dann die Eingabedatei lesen, die betreffenden Elemente (Events) einfügen. Das dürfte zigmal schneller gehen, als jedesmal einen DOM zu parsen.
 
Ich würde so ein Problem eher wie von @fhoffmann vorgeschlagen angehen. Wenn da noch XML verarbeitet werden muss, dann würde sich bei den Mengen ggf. StAX lohnen. Die Ausgabedatei würde ich nur einmal öffnen, dann die Eingabedatei lesen, die betreffenden Elemente (Events) einfügen. Das dürfte zigmal schneller gehen, als jedesmal einen DOM zu parsen.
Ich würde so ein Problem eher wie von @fhoffmann vorgeschlagen angehen. Wenn da noch XML verarbeitet werden muss, dann würde sich bei den Mengen ggf. StAX lohnen. Die Ausgabedatei würde ich nur einmal öffnen, dann die Eingabedatei lesen, die betreffenden Elemente (Events) einfügen. Das dürfte zigmal schneller gehen, als jedesmal einen DOM zu parsen.
Wenn ich Cods andres schreibe, dann wird ganze Struktur in der Datei geändert und die Anforderung auf die Datei wird nicht mehr erfüllt…
 
Ehrlich gesagt, nicht ganz, aber hat funktioniert. Wenn die von mir eingegebenen Werten ganz schlimm sind und es gäbe andere bessere Möglichkeit, würde mir sehr geholfen
-Xmx128m -Xms7096m stellt den maximalen Speicher Xmx auf 128MB und den initialen Speicher Xms auf 7096m. Damit überschreibst du den maximalen Wert, und zusätzlich brauchst du direkt vom Start an 7Gb Speicher, völlig egal, wie viel wirklich gebraucht wird.

-Xmx7g wäre da eine sinnvolle Alternative, dann wird bis zu 7GB benutzt, je nachdem wie viel gebraucht wird.

Wenn ich Cods andres schreibe, dann wird ganze Struktur in der Datei geändert und die Anforderung auf die Datei wird nicht mehr erfüllt…
Man kann den Code auch anders schreiben und trotzdem eine korrekte Ausgabe erhalten...
 
-Xmx128m -Xms7096m stellt den maximalen Speicher Xmx auf 128MB und den initialen Speicher Xms auf 7096m. Damit überschreibst du den maximalen Wert, und zusätzlich brauchst du direkt vom Start an 7Gb Speicher, völlig egal, wie viel wirklich gebraucht wird.

-Xmx7g wäre da eine sinnvolle Alternative, dann wird bis zu 7GB benutzt, je nachdem wie viel gebraucht wird.


Man kann den Code auch anders schreiben und trotzdem eine korrekte Ausgabe erhalten...
Das stimmt, man kann selbstverständlich, aber zeitlich bin sehr beschränkt und das Code umzuschreiben würde von mir richtig viel Zeit verlangen.

viele Dank für gute Erklärung.
 
Das stimmt, man kann selbstverständlich, aber zeitlich bin sehr beschränkt und das Code umzuschreiben würde von mir richtig viel Zeit verlangen.
Das war von mir nur als allgemeiner Hinweis gemeint. Nachdem Du das Problem bereits durch mehr Heap gelöst hattest, war ich jetzt nicht davon ausgegangen, dass Du alles neu schreiben würdest, nur um die Dateien nochmal zu mergen 🙂

Wenn man mal eben eine "Einmal"-Lösung braucht, ist Quick & Dirty nicht nur ok, sondern gut. Nicht lange mit Code aufhalten, in Deinem Fall Heap hochsetzen, Thema erledigt. Da spielen Fehlerbehandlung oder eben der Ressourcenverbrauch keine Rolle, zur Not wechselt man auf einen besser ausgestatteten Rechner. Solltest Du das aber öfter machen müssen, womöglich mit noch größeren Datenmengen oder Dein Tool auf Rechnern ausgeführt werden müssen, die nicht so stark auf der Brust sind, dann lohnt sich die Optimierung des Codes.
 
Das war von mir nur als allgemeiner Hinweis gemeint. Nachdem Du das Problem bereits durch mehr Heap gelöst hattest, war ich jetzt nicht davon ausgegangen, dass Du alles neu schreiben würdest, nur um die Dateien nochmal zu mergen 🙂

Wenn man mal eben eine "Einmal"-Lösung braucht, ist Quick & Dirty nicht nur ok, sondern gut. Nicht lange mit Code aufhalten, in Deinem Fall Heap hochsetzen, Thema erledigt. Da spielen Fehlerbehandlung oder eben der Ressourcenverbrauch keine Rolle, zur Not wechselt man auf einen besser ausgestatteten Rechner. Solltest Du das aber öfter machen müssen, womöglich mit noch größeren Datenmengen oder Dein Tool auf Rechnern ausgeführt werden müssen, die nicht so stark auf der Brust sind, dann lohnt sich die Optimierung des Codes.
Klar, auf jeden Fall lohnt sich, wenn ich mein Problem so nicht gelöst hätte, dann hätte ich Code umschreiben müssen. Darum geht es auch diesem Post…

ich bin sehr dankbar bei jedem, der hier ein Kommentar geschrieben hat
 

Zurück
Oben