8

多くのxmlファイルを1つにマージしようとしています。私はDOMでこれを正常に実行しましたが、このソリューションはいくつかのファイルに制限されています。1000を超える複数のファイルで実行すると、java.lang.OutOfMemoryErrorが発生します。

私が達成したいのは、次のファイルがある場所です

ファイル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、saxで多くの実験をしましたが、何かが足りないようです。どんな助けでも大歓迎です

4

6 に答える 6

10

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

マイナーな注意点の1つは、このAPIが空のタグを台無しにして、に変更<foo/>されているように見えること<foo></foo>です。

于 2012-05-25T19:22:06.543 に答える
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();
        }
于 2012-05-25T19:00:19.797 に答える
2

DOMは、ドキュメント全体をメモリに保持する必要があります。タグに対して特別な操作を行う必要がない場合は、InputStreamを使用してすべてのファイルを読み取るだけです。いくつかの操作を行う必要がある場合は、SAXを使用してください。

于 2012-05-25T18:50:14.467 に答える
2

Domは大量のメモリを消費します。私には、次の選択肢があります。

最良の方法はSAXを使用することです。saxを使用すると、使用されるメモリの量はごくわずかです。基本的に、ほぼ1つの要素が常に入力から出力に移動するため、メモリフットプリントは非常に低くなります。ただし、サックスの使用はそれほど単純ではありません。domと比較すると、少し直感に反します。

自分で試したのではなく、Staxを試してみてください。これは、ステロイドのサックスの一種であり、実装と使用が簡単です。制御できないサックスイベントを受信するのではなく、実際に「ソースに問い合わせて」、必要な要素をストリーミングします。そのため、domとsaxの中間に収まり、saxと同様のメモリフットプリントがありますが、よりフレンドリーなパラダイムです。

Sax、stax、domはすべて、名前空間やその他のXMLの奇妙な点を正しく保存、宣言する場合に重要です。

ただし、名前空間にも準拠している可能性のある、迅速で汚い方法が必要な場合は、単純な古い文字列とライターを使用してください。

「大きな」ドキュメントの宣言とルート要素の出力をFileWriterに開始します。次に、必要に応じてdomを使用して、各ファイルをロードします。「大きな」ファイルに入れたい要素を選択し、それらを文字列にシリアル化して戻し、ライターに送信します。ライターは大量のメモリを使用せずにディスクにフラッシュし、domは反復ごとに1つのドキュメントのみをロードします。入力側にも非常に大きなファイルがある場合、または携帯電話で実行する予定がない限り、メモリの問題はそれほど多くありません。domが正しくシリアル化すると、名前空間宣言などが保持され、コードは投稿した行よりも多くの行になります。

于 2012-05-25T19:00:58.837 に答える
1

この種の作業では、DOMを使用しないことをお勧めします。ファイルの内容を読み取り、サブストリングを作成する方が簡単で十分です。

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

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

次に、多くのメモリ消費を回避します。BufferedWritterたとえば、xmlを抽出するたびにメインファイルに書き込みます。パフォーマンスを向上させるために、java.nioを使用することもできます。

于 2012-05-25T18:58:17.490 に答える
1

あなたがしていることは正しいと思います。非常に膨大な数のファイルに拡張する唯一の方法は、ストリーミングでテキストベースのアプローチを使用することです。そのため、すべてをメモリに保持することはできません。しかし、ねえ!朗報です。最近のメモリは安価で、64ビットJVMが大流行しているため、必要なのはヒープサイズを増やすことだけかもしれません。-Xms1g JVMオプションを使用してプログラムを再実行してみてください(1Gbの初期ヒープサイズを割り当てます)。

また、すべてのDOM要件にXOMを使用する傾向があります。試してごらん。はるかに効率的です。メモリ要件についてはよくわかりませんが、私の経験では桁違いに高速です。

于 2012-05-25T19:12:04.203 に答える