it-swarm.com.de

So analysieren Sie XML-Dateien, die zu groß sind, um in den Speicher zu passen

Ich versuche, XML-Dateien zu verarbeiten, die zu groß sind, um in den Speicher zu passen. Ihre Größe reicht von Dutzenden von Megabyte bis zu über 120 GB. Bei meinem ersten Versuch las ich die Dateien als einfachen Text in Blöcken von jeweils einigen tausend Zeichen und suchte nach einzelnen abgeschlossenen XML-Tags in den kleinen String-Blöcken:

FileReader fileReader;
    try {
        fileReader = new FileReader(file);

        DocumentBuilder factory = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document doc;

        int charsToReadAtOnce = 1000;
        char[] readingArray = new char[charsToReadAtOnce ];
        int readOffset = 0;
        StringBuilder buffer = new StringBuilder(charsToReadAtOnce * 3);

        while(fileReader.read(readingArray, readOffset, charsToReadAtOnce ) != -1) {
            buffer.append(new String(readingArray));
            String current = buffer.toString();

            doc = factory.parse(new InputSource(new StringReader(buffer.toString())));

            //see if string contains a complete XML tag
            //if so, save useful info and manually clear it
        }
    } catch (ALL THE EXCEPTIONS...

Dies wurde schnell chaotisch und kompliziert mit vielen Edge-Fällen wie Tags mit einer Länge von mehr als 1000 Zeichen und dem Ignorieren von Start- und End-Tags. Anstatt weiterzumachen, möchte ich einen weniger schmerzhaften Algorithmus verwenden, kann aber keinen wirklich guten finden. Hat Java eine geeignetere Möglichkeit, mit massiven XML-Dateien wie diesen umzugehen? Als ich diese Frage stellte, stieß ich auf Lesen Sie eine komprimierte XML mit .NET . Ich denke etwas so, aber offensichtlich für Java könnte für mich funktionieren, aber ich weiß nicht, ob es existiert?

6
ObvEng

Streaming-API (wie SAX siehe https://docs.Oracle.com/javase/tutorial/jaxp/sax/ ) vs DOM-APIs. Ersterer Prozess-Tag, sobald er auftritt, während letzterer das gesamte DOM-Modell im Speicher darstellt. Siehe auch https://stackoverflow.com/q/6828703/7441

11
YoYo

Während YoYo eine gute Antwort geliefert hat, die für fast jede Anwendung funktioniert, habe ich etwas, das möglicherweise besser ist als reine Streaming-Parser.

Stellen Sie sich zum Beispiel eine Datei mit einer Milliarde Bankkonten vor:

<accounts>
    <account>
        <id>1</id>
        <balance>123.45</balance>
    </account>
    ....
</accounts>

Jetzt können Sie die gesamte Datei mit einem Streaming-Parser analysieren. Das Problem ist jedoch, dass Streaming-Parser umständlich zu verwenden sind und Tree-Parser viel besser zu verwenden sind. Leider können Sie aus Speichergründen nicht die gesamte Datei in einem großen Baum darstellen.

Die Lösung besteht darin, dass ein "<account>" - Tag angezeigt wird, um den Baumsammlungsmodus zu aktivieren, und wenn ein "</ account>" - Tag den Baum finalisiert, sodass Sie den folgenden Baum haben, wenn Sie auf das schließende Tag stoßen:

<account>
    <id>1</id>
    <balance>123.45</balance>
</account>

Dann können Sie auf bequeme Weise als Baum auf das Fragment des Dokuments zugreifen, aber das gesamte Dokument befindet sich nicht als ein großer Baum im Speicher.

Sie müssen einen solchen Baumsammlungscode auf einem Streaming-Parser entwickeln.

3
juhist

Ich habe einen Codegenerator erstellt, um dieses spezielle Problem zu lösen (eine frühe Version wurde 2008 konzipiert). Grundsätzlich hat jedes complexType sein Java POJO Äquivalent und Handler für den jeweiligen Typ werden aktiviert, wenn sich der Kontext zu diesem Element ändert. Ich habe diesen Ansatz für SEPA, Transaction Banking und zum Beispiel Discogs (30 GB) verwendet. Sie können angeben, welche Elemente zur Laufzeit verarbeitet werden sollen, und zwar deklarativ mithilfe einer Propeties-Datei.

XML2J verwendet die Zuordnung von complexTypes zu Java POJOs einerseits, aber Sie können Ereignisse angeben, die Sie abhören möchten.

account/@process = true
account/accounts/@process = true
account/accounts/@detach = true

Die Essenz ist in der dritten Zeile. Das Trennen stellt sicher, dass einzelne Konten nicht zur Kontenliste hinzugefügt werden. Es wird also nicht überlaufen.

class AccountType {
    private List<AccountType> accounts = new ArrayList<>();

    public void addAccount(AccountType tAccount) {
        accounts.add(tAccount);
    }
    // etc.
};

In Ihrem Code müssen Sie die Prozessmethode implementieren (standardmäßig generiert der Codegenerator eine leere Methode:

class AccountsProcessor implements MessageProcessor {
    static private Logger logger = LoggerFactory.getLogger(AccountsProcessor.class);

    // assuming Spring data persistency here
    final String path = new ClassPathResource("spring-config.xml").getPath();
    ClassPathXmlApplicationContext context = new   ClassPathXmlApplicationContext(path);
    AccountsTypeRepo repo = context.getBean(AccountsTypeRepo.class);


    @Override
    public void process(XMLEvent evt, ComplexDataType data)
        throws ProcessorException {

        if (evt == XMLEvent.END) {
            if( data instanceof AccountType) {
                process((AccountType)data);
            }
        }
    }

    private void process(AccountType data) {
        if (logger.isInfoEnabled()) {
            // do some logging
        }
        repo.save(data);
    }
}   

Beachten Sie, dass XMLEvent.END markiert das schließende Tag eines Elements. Wenn Sie es also verarbeiten, ist es vollständig. Wenn Sie es (mithilfe eines FK) mit dem übergeordneten Objekt in der Datenbank verknüpfen müssen, können Sie das XMLEvent.BEGIN Erstellen Sie für das übergeordnete Element einen Platzhalter in der Datenbank und speichern Sie ihn mit jedem seiner untergeordneten Schlüssel. Im Finale XMLEvent.END Sie würden dann das übergeordnete Element aktualisieren.

Beachten Sie, dass der Codegenerator alles generiert, was Sie benötigen. Sie müssen nur diese Methode und natürlich den DB-Klebercode implementieren.

Es gibt Beispiele, mit denen Sie beginnen können. Der Codegenerator generiert sogar Ihre POM-Dateien, sodass Sie Ihr Projekt sofort nach der Generierung erstellen können.

Die Standardprozessmethode lautet wie folgt:

@Override
public void process(XMLEvent evt, ComplexDataType data)
    throws ProcessorException {


/*
 *  TODO Auto-generated method stub implement your own handling here.
 *  Use the runtime configuration file to determine which events are to be sent to the processor.
 */ 

    if (evt == XMLEvent.END) {
        data.print( ConsoleWriter.out );
    }
}

Downloads:

Zuerst mvn clean install der Kern (er muss sich im lokalen Maven-Repo befinden), dann der Generator. Und vergessen Sie nicht, die Umgebungsvariable XML2J_HOME gemäß den Anweisungen im Handbuch.

1
dexter