Come analizzare i file XML troppo grandi per adattarli alla memoria

6

Sto cercando di elaborare file XML troppo grandi per adattarli alla memoria. Hanno dimensioni che vanno da decine di megabyte a oltre 120 GB. Il mio primo tentativo mi ha fatto leggere i file come testo semplice, in blocchi di poche migliaia di caratteri alla volta, e cercare singoli tag XML completati nei pochi pezzi di 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...

Questo si stava rivelando complicato e veloce con un sacco di casi limite come tag con oltre 1000 caratteri e ignorando i tag di inizio e fine. Invece di andare avanti, voglio usare un algoritmo meno doloroso ma non riesco a crearne uno veramente valido. Java ha un modo più appropriato per gestire enormi file XML come questi? Mentre faccio questa domanda, mi sono imbattuto in Leggi un xml compresso con .NET . Penso che qualcosa del genere, ma ovviamente per Java potrebbe funzionare per me, ma non so se esiste?

    
posta ObvEng 12.03.2017 - 03:06
fonte

3 risposte

11

API di streaming (come SAX, vedi link ) vs DOM api. Un ex tag di processo mentre si verificano, mentre il secondo rappresenta l'intero modello DOM in memoria. Vedi anche link

    
risposta data 12.03.2017 - 03:57
fonte
3

Mentre YoYo ha fornito una buona risposta che funziona per quasi tutte le applicazioni, ho qualcosa che potrebbe essere migliore dei puri parser di streaming.

Si consideri, ad esempio, un file con un miliardo di conti bancari:

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

Ora puoi analizzare questo intero file con un parser di streaming. Il problema, tuttavia, è che i parser di streaming sono scomodi da utilizzare e che i parser degli alberi sono molto più utili da utilizzare. Sfortunatamente, a causa di vincoli di memoria, non puoi rappresentare l'intero file in un unico grande albero.

La soluzione è quando vedi un "< account >" tag per attivare la modalità di raccolta ad albero e quando visualizzi un "< / account >" tag finalizza l'albero in modo tale che avrai il seguente albero ogni volta che incontri il tag di chiusura:

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

Quindi puoi accedere al frammento del documento come un albero in un modo conveniente, ma l'intero documento non è nella memoria come un grande albero.

Dovrai sviluppare questo codice di raccolta dell'albero sopra un parser di streaming.

    
risposta data 12.03.2017 - 09:30
fonte
0

Ho creato un generatore di codice progettato per risolvere questo particolare problema (una versione anticipata è stata concepita nel 2008). Fondamentalmente ogni complexType ha il suo Java POJO equivalente ei gestori per il particolare tipo vengono attivati quando il contesto cambia in quell'elemento. Ho usato questo approccio per SEPA, operazioni bancarie e per esempio discog (30 GB). È possibile specificare quali elementi si desidera elaborare in fase di esecuzione, in modo dichiarativo utilizzando un file di proprietà.

XML2J utilizza da un lato il mapping di complexTypes con i POJO Java, ma consente di specificare gli eventi che si desidera ascoltare. Per es.

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

L'essenza è nella terza riga. Il distacco assicura che i singoli account non vengano aggiunti all'elenco degli account. In questo modo non si ecciterà.

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

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

Nel codice è necessario implementare il metodo di processo (per impostazione predefinita il generatore di codice genera un metodo vuoto:

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

Tieni presente che XMLEvent.END contrassegna il tag di chiusura di un elemento. Quindi, quando lo stai elaborando, è completo. Se devi metterlo in relazione (usando un FK) al suo oggetto padre nel database, puoi elaborare XMLEvent.BEGIN per il genitore, creare un segnaposto nel database e usare la sua chiave per memorizzare con ciascuno dei suoi figli. Nell'ultimo XMLEvent.END dovresti quindi aggiornare il genitore.

Si noti che il generatore di codice genera tutto ciò di cui si ha bisogno. Devi solo implementare quel metodo e ovviamente il codice della colla DB.

Ci sono dei campioni per iniziare. Il generatore di codice genera anche i tuoi file POM, quindi puoi creare subito dopo la generazione il tuo progetto.

Il metodo di processo predefinito è simile a questo:

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

Download:

Primo mvn clean install del core (deve essere nel repository locale dei servizi), quindi il generatore. E non dimenticare di impostare la variabile di ambiente XML2J_HOME come da indicazioni nell'usuale.

    
risposta data 28.04.2018 - 19:41
fonte

Leggi altre domande sui tag