web-dev-qa-db-fra.com

Comment analyser des fichiers XML trop volumineux pour tenir en mémoire

J'essaie de traiter des fichiers XML qui sont trop gros pour tenir en mémoire. Leur taille varie de dizaines de mégaoctets à plus de 120 Go. Ma première tentative m'a amené à lire les fichiers en texte brut, en blocs de quelques milliers de caractères à la fois, et à rechercher des balises XML individuelles terminées dans les petits blocs String:

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...

Cela devenait rapide et compliqué rapidement avec de nombreux cas Edge comme des balises de plus de 1000 caractères et ignorant les balises de début et de fin. Au lieu d'appuyer sur, je veux utiliser un algorithme moins douloureux mais je ne peux pas en trouver un très bon. Java a-t-il un moyen plus approprié de gérer des fichiers XML massifs comme ceux-ci? En posant cette question, je suis tombé sur Lire un xml zippé avec .NET . Je pense que quelque chose comme ça mais évidemment pour Java pourrait fonctionner pour moi mais je ne sais pas s'il existe?

6
ObvEng

API de streaming (telle que SAX voir https://docs.Oracle.com/javase/tutorial/jaxp/sax/ ) vs API de DOM. Le premier traite les balises au fur et à mesure qu'elles se produisent, tandis que le dernier représente le modèle DOM entier en mémoire. Voir aussi https://stackoverflow.com/q/6828703/7441

11
YoYo

Bien que YoYo ait fourni une bonne réponse qui fonctionne pour presque toutes les applications, j'ai quelque chose de mieux que de simples analyseurs de streaming.

Prenons, par exemple, un fichier qui compte un milliard de comptes bancaires:

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

Maintenant, vous pouvez analyser tout ce fichier avec un analyseur en streaming. Le problème, cependant, est que les analyseurs en streaming sont lourds à utiliser et que les analyseurs d'arbre sont beaucoup plus agréables à utiliser. Malheureusement, en raison de contraintes de mémoire, vous ne pouvez pas représenter le fichier entier dans une seule grande arborescence.

La solution consiste à voir une balise "<account>" pour activer le mode de collecte d'arborescence et à voir une balise "</account>" finaliser l'arborescence afin que vous ayez l'arborescence suivante chaque fois que vous rencontrez la balise de fermeture:

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

Ensuite, vous pouvez accéder au fragment du document sous forme d'arborescence d'une manière pratique, mais le document entier n'est pas dans la mémoire comme une seule grande arborescence.

Vous devrez développer un tel code de collecte d'arborescence au-dessus d'un analyseur en streaming.

3
juhist

J'ai créé un générateur de code conçu pour résoudre ce problème particulier (une première version a été conçue en 2008). Fondamentalement, chaque complexType a son Java POJO équivalent et les gestionnaires pour le type particulier sont activés lorsque le contexte change pour cet élément. J'ai utilisé cette approche pour le SEPA, les transactions bancaires et par exemple les discogs (30 Go). Vous pouvez spécifier les éléments que vous souhaitez traiter lors de l'exécution, de manière déclarative à l'aide d'un fichier de propriétés.

XML2J utilise le mappage de complexTypes vers Java POJO d'une part, mais vous permet de spécifier les événements que vous souhaitez écouter. Par ex.

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

L'essence est dans la troisième ligne. Le détachement garantit que les comptes individuels ne sont pas ajoutés à la liste des comptes. Il ne débordera donc pas.

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

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

Dans votre code, vous devez implémenter la méthode de processus (par défaut, le générateur de code génère une méthode vide:

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);
    }
}   

Notez que XMLEvent.END marque la balise de fermeture d'un élément. Ainsi, lorsque vous le traitez, il est terminé. Si vous devez l'associer (à l'aide d'un FK) à son objet parent dans la base de données, vous pouvez traiter le XMLEvent.BEGIN pour le parent, créez un espace réservé dans la base de données et utilisez sa clé pour stocker avec chacun de ses enfants. Dans la finale XMLEvent.END vous mettriez alors à jour le parent.

Notez que le générateur de code génère tout ce dont vous avez besoin. Il vous suffit d'implémenter cette méthode et bien sûr le code de colle DB.

Il existe des exemples pour vous aider à démarrer. Le générateur de code génère même vos fichiers POM, vous pouvez donc immédiatement après la génération créer votre projet.

La méthode de traitement par défaut est la suivante:

@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 );
    }
}

Téléchargements:

Première mvn clean install le noyau (il doit être dans le référentiel maven local), puis le générateur. Et n'oubliez pas de configurer la variable d'environnement XML2J_HOME selon les instructions du manuel d'utilisation.

1
dexter