2012-05-25 1 views
8

Sto cercando di unire molti file XML in uno. L'ho fatto con successo in DOM, ma questa soluzione è limitata a pochi file. Quando lo eseguo su più file> 1000, ottengo un java.lang.OutOfMemoryError.Come unire> 1000 file xml in uno utilizzando Java

Quello che voglio ottenere è dove ho i seguenti file

file di 1:

<root> 
.... 
</root> 

file di 2:

<root> 
...... 
</root> 

n file:

<root> 
.... 
</root> 

risultante in: uscita:

<rootSet> 
<root> 
.... 
</root> 
<root> 
.... 
</root> 
<root> 
.... 
</root> 
</rootSet> 

Questo è il mio attuale implementazione:

DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); 
    DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); 
    Document doc = docBuilder.newDocument(); 
    Element rootSetElement = doc.createElement("rootSet"); 
    Node rootSetNode = doc.appendChild(rootSetElement); 
    Element creationElement = doc.createElement("creationDate"); 
    rootSetNode.appendChild(creationElement); 
    creationElement.setTextContent(dateString); 
    File dir = new File("/tmp/rootFiles"); 
    String[] files = dir.list(); 
    if (files == null) { 
     System.out.println("No roots to merge!"); 
    } else { 
     Document rootDocument; 
      for (int i=0; i<files.length; i++) { 
         File filename = new File(dir+"/"+files[i]);   
       rootDocument = docBuilder.parse(filename); 
       Node tempDoc = doc.importNode((Node) Document.getElementsByTagName("root").item(0), true); 
       rootSetNode.appendChild(tempDoc); 
     } 
    } 

ho sperimentato molto con XSLT, sax, ma mi sembra di tenere manca qualcosa. Qualsiasi aiuto sarebbe molto apprezzato

+4

C'è qualche ragione per cui è necessario mantenere il DOM in memoria? Hai bisogno di più di una semplice concatenazione di stringhe in questo caso? –

+1

la semplice concatenazione mantiene la dichiarazione xml se ogni singolo file xml viene unito. In effetti, in linea di principio, sto cercando una semplice concatenazione di file xml. – Andra

+2

Perché non inserire più file XML in un unico archivio? Finisce come un file. Rendilo non compresso se la velocità di lettura/scrittura è importante, compressa se la dimensione del file o la larghezza di banda sono più importanti. –

risposta

8

Si potrebbe anche considerare l'utilizzo di StAX. Ecco il codice che avrebbe fatto ciò che si vuole:

import java.io.File; 
import java.io.FileWriter; 
import java.io.Writer; 

import javax.xml.stream.XMLEventFactory; 
import javax.xml.stream.XMLEventReader; 
import javax.xml.stream.XMLEventWriter; 
import javax.xml.stream.XMLInputFactory; 
import javax.xml.stream.XMLOutputFactory; 
import javax.xml.stream.events.XMLEvent; 
import javax.xml.transform.stream.StreamSource; 

public class XMLConcat { 
    public static void main(String[] args) throws Throwable { 
     File dir = new File("/tmp/rootFiles"); 
     File[] rootFiles = dir.listFiles(); 

     Writer outputWriter = new FileWriter("/tmp/mergedFile.xml"); 
     XMLOutputFactory xmlOutFactory = XMLOutputFactory.newFactory(); 
     XMLEventWriter xmlEventWriter = xmlOutFactory.createXMLEventWriter(outputWriter); 
     XMLEventFactory xmlEventFactory = XMLEventFactory.newFactory(); 

     xmlEventWriter.add(xmlEventFactory.createStartDocument()); 
     xmlEventWriter.add(xmlEventFactory.createStartElement("", null, "rootSet")); 

     XMLInputFactory xmlInFactory = XMLInputFactory.newFactory(); 
     for (File rootFile : rootFiles) { 
      XMLEventReader xmlEventReader = xmlInFactory.createXMLEventReader(new StreamSource(rootFile)); 
      XMLEvent event = xmlEventReader.nextEvent(); 
      // Skip ahead in the input to the opening document element 
      while (event.getEventType() != XMLEvent.START_ELEMENT) { 
       event = xmlEventReader.nextEvent(); 
      } 

      do { 
       xmlEventWriter.add(event); 
       event = xmlEventReader.nextEvent(); 
      } while (event.getEventType() != XMLEvent.END_DOCUMENT); 
      xmlEventReader.close(); 
     } 

     xmlEventWriter.add(xmlEventFactory.createEndElement("", null, "rootSet")); 
     xmlEventWriter.add(xmlEventFactory.createEndDocument()); 

     xmlEventWriter.close(); 
     outputWriter.close(); 
    } 
} 

Un avvertimento minore è che questa API sembra pasticciare con tag vuoti, cambiando <foo/> in <foo></foo>.

2

DOM deve conservare l'intero documento in memoria. Se non è necessario eseguire alcuna operazione speciale con i tag, utilizzare semplicemente un InputStream e leggere tutti i file. Se è necessario eseguire alcune operazioni, utilizzare SAX.

1

Per questo tipo di lavoro suggerisco di non utilizzare DOM, leggere il contenuto del file e rendere la sottostringa più semplice e sufficiente.

Sto pensando a qualcosa di simile:

String rootContent = document.substring(document.indexOf("<root>"), document.lastIndexOf("</root>")+7); 

Poi, per evitare di molto consumazione di memoria. Scrivi nel file principale dopo ogni estrazione xml con un BufferedWritter per esempio. Per prestazioni migliori puoi anche usare java.nio.

3

Basta eseguire l'analisi xml in quanto non sembra richiedere alcun parsing effettivo di xml.

Per l'efficienza fare qualcosa di simile:

File dir = new File("/tmp/rootFiles"); 
String[] files = dir.list(); 
if (files == null) { 
    System.out.println("No roots to merge!"); 
} else { 
     try (FileChannel output = new FileOutputStream("output").getChannel()) { 
      ByteBuffer buff = ByteBuffer.allocate(32); 
      buff.put("<rootSet>\n".getBytes()); // specify encoding too 
      buff.flip(); 
      output.write(buff); 
      buff.clear(); 
      for (String file : files) { 
       try (FileChannel in = new FileInputStream(new File(dir, file).getChannel()) { 
        in.transferTo(0, 1 << 24, output); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } 
      } 
      buff.put("</rootSet>\n".getBytes()); // specify encoding too 
      buff.flip(); 
      output.write(buff); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
2

Dom fa consumare molta memoria. Hai, imho, le seguenti alternative.

Il migliore è usare SAX. Usando il sax, viene utilizzata solo una piccola quantità di memoria, poiché praticamente un singolo elemento sta viaggiando dall'input all'output in un dato momento, quindi il footprint della memoria è estremamente basso. Tuttavia, usare il sax non è così semplice, perché rispetto a dom è un po 'controintuitivo.

Prova Stax, non provato da solo, ma è una specie di sax su steroidi più facile da implementare e da usare, perché al contrario di ricevere solo eventi di sassofono che non controlli, in realtà "chiedi alla fonte" di trasmetterti in streaming gli elementi che desideri, quindi si adattano a metà tra dom e sax, hanno un'impronta di memoria simile al sax, ma un paradigma più amichevole.

Sax, stax, dom sono tutti importanti se si desidera conservare correttamente, dichiarare ecc. Spazi dei nomi e altre stranezze XML.

Tuttavia, se avete solo bisogno di un modo veloce e sporco, che probabilmente sarà anche conforme allo spazio dei nomi, usate semplici stringhe e scrittori.

Iniziare l'output su FileWriter della dichiarazione e dell'elemento radice del documento "grande". Quindi carica, usando dom se vuoi, ogni singolo file. Seleziona gli elementi che desideri includere nel file "grande", serializzali in una stringa e invialo allo scrittore. lo scrittore eseguirà il flush su disco senza utilizzare un'enorme quantità di memoria e dom caricherà solo un documento per iterazione. A meno che tu non abbia file molto grandi sul lato di input, o progetti di eseguirlo su un cellulare, non dovresti avere molti problemi di memoria. Se dom lo serializza correttamente, dovrebbe conservare le dichiarazioni dello spazio dei nomi e simili, e il codice sarà solo un po 'più di righe rispetto a quello che hai postato.

1

Penso che quello che stai facendo sia valido. L'unico modo per ridimensionare un numero davvero elevato di file consiste nell'utilizzare un approccio basato sul testo con lo streaming, in modo da non tenere mai tutto in memoria. Ma hey! Buone notizie. Oggigiorno la memoria è a buon mercato e le JVM a 64 bit sono di gran moda, quindi forse tutto ciò che serve è aumentare la dimensione dell'heap. Prova a rieseguire il programma con l'opzione JSM -Xms1g (alloca 1 GB di dimensione heap iniziale).

Tendo anche a utilizzare XOM per tutti i miei requisiti DOM. Provaci. Molto più efficiente. Non so per certo sui requisiti di memoria, ma i suoi ordini di grandezza più veloce nella mia esperienza.