2012-05-25 8 views
8

多くのxmlファイルを1つにマージしようとしています。 DOMで成功しましたが、この解決策はいくつかのファイルに限られています。私が複数のファイル> 1000で実行すると、java.lang.OutOfMemoryErrorが得られます。私は、次のファイルを持っているところ> 1000 xmlファイルをJavaを使って1つにマージする方法

は何を達成したいことはある

ファイル1:

<root> 
.... 
</root> 

ファイル2:

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

ファイルN:

<root> 
.... 
</root> 

その結果: 出力:

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

これは私の現在の実装である:

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

私は、XSLT、サックスで多くのことを経験してきたが、私は何かが欠けておくように見えます。どんな助けも高く評価されます

+4

DOMを実際にメモリに保持する必要がありますか?この場合、単純な文字列連結以上のものが必要ですか? –

+1

単純な連​​結は、個々のxmlファイルがマージされている場合にxml宣言を保持します。ビット実際には、私はXMLファイルの単純な連結を探しています。 – Andra

+2

複数のXMLファイルを1つのアーカイブに入れてみませんか?それは1つのファイルとして終わります。読み取り/書き込み速度が重要で、ファイルサイズや帯域幅が重要な場合は、圧縮して圧縮しないでください。 –

答えて

8

また、StAXの使用を検討することもできます。ここで何をしたいだろうコードです:

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

一つのマイナーな注意点が<foo></foo><foo/>を変え、このAPIは空のタグを台無しに思われることです。

2

DOMは文書全体をメモリに保存する必要があります。あなたのタグで特別な操作を行う必要がない場合は、単にInputStreamを使用してすべてのファイルを読み込みます。いくつかの操作を行う必要がある場合は、SAXを使用します。

1

この種の作業では、DOMを使用しないように、ファイルの内容を読み、部分文字列を作るのは簡単で十分です。

私はそのようなことを考えています:

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

そして、多くのメモリの成就に避けること。たとえば、XMLファイルを抽出するたびに、メインファイルにBufferedWritterと書き込んでください。パフォーマンスを向上させるには、java.nioも使用できます。

3

xmlの実際の解析を必要としないように見えるので、xml解析を行わずにそのまま実行してください。効率のため

はこのような何かを:

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

ドムは、メモリを大量に消費しません。あなたには、次の選択肢があります。

最高のものはSAXを使用することです。サックスを使用すると、非常に少量のメモリしか使用されないため、基本的に1つの要素が特定の時点で入力から出力に移動するため、メモリ占有量が極端に少なくなります。しかし、サックスを使用することはそれほど単純ではありません。なぜなら、ドームと比べると少し直感的ではありません。

自分で試してみませんが、ステロイドでサックスのようなものを試してみましょう。実際にはあなたがコントロールしていないサックスイベントを受信するのではなく、あなたが望む要素は、domとsaxの中間に収まるので、saxに似たメモリフットプリントがありますが、よりフレンドリーなパラダイムです。

ネームスペースやその他のXMLの奇妙さを正しく保存したり、宣言したりする場合は、Sax、stax、domはすべて重要です。

しかし、おそらくネームスペースに準拠した素早く汚れた方法が必要な場合は、普通の古い文字列とライターを使用してください。

"大きな"ドキュメントの宣言とルート要素をFileWriterに出力し始めます。それから、必要に応じてdomを使用して、各単一のファイルをロードします。 "big"ファイルで終わりたい要素を選択し、それらを文字列にシリアル化して、ライターに送ります。ライターは膨大なメモリを使用せずにディスクにフラッシュし、domは繰り返しごとに1つのドキュメントのみを読み込みます。入力側に非常に大きなファイルがある場合や携帯電話で実行する場合を除き、多くのメモリに問題はありません。 domが正しく直列化すると、名前空間の宣言などが保持され、コードはあなたが投稿したものよりも多くの行になります。

1

あなたがしていることは有効だと思います。実際に膨大な数のファイルに拡張する唯一の方法は、ストリーミングでテキストベースのアプローチを使用することです。しかし、ちょっと!良いニュース。最近はメモリが安く、64ビットのJVMが激怒しているので、ヒープサイズを増やすだけで十分でしょう。プログラムを-Xms1g JVMオプション(1Gbの初期ヒープサイズを割り当てる)で再実行してみてください。

また、すべてのDOM要件にXOMを使用する傾向があります。試してごらん。はるかに効率的です。私の経験では、メモリ要件については確かに分かりませんが、そのオーダーはもっと速くなります。

関連する問題