web-dev-qa-db-ja.com

JAXBは大きなXMLファイルをチャンクで解析できますか

潜在的に大きなXMLファイルを解析する必要があります。そのスキーマはすでにいくつかのXSDファイルで提供されているため、XMLバインディングが非常に好まれます。 JAXBを使用してファイルをチャンクで解析できるかどうか、またその場合はその方法を知りたいのですが。

23
John F.

コードが重要なので、大きなファイルをチャンクに読み取るPartialUnmarshallerがあります。そのように使用できますnew PartialUnmarshaller<YourClass>(stream, YourClass.class)

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.*;
import Java.io.InputStream;
import Java.util.List;
import Java.util.NoSuchElementException;
import Java.util.stream.Collectors;
import Java.util.stream.IntStream;

import static javax.xml.stream.XMLStreamConstants.*;

public class PartialUnmarshaller<T> {
    XMLStreamReader reader;
    Class<T> clazz;
    Unmarshaller unmarshaller;

    public PartialUnmarshaller(InputStream stream, Class<T> clazz) throws XMLStreamException, FactoryConfigurationError, JAXBException {
        this.clazz = clazz;
        this.unmarshaller = JAXBContext.newInstance(clazz).createUnmarshaller();
        this.reader = XMLInputFactory.newInstance().createXMLStreamReader(stream);

        /* ignore headers */
        skipElements(START_DOCUMENT, DTD);
        /* ignore root element */
        reader.nextTag();
        /* if there's no tag, ignore root element's end */
        skipElements(END_ELEMENT);
    }

    public T next() throws XMLStreamException, JAXBException {
        if (!hasNext())
            throw new NoSuchElementException();

        T value = unmarshaller.unmarshal(reader, clazz).getValue();

        skipElements(CHARACTERS, END_ELEMENT);
        return value;
    }

    public boolean hasNext() throws XMLStreamException {
        return reader.hasNext();
    }

    public void close() throws XMLStreamException {
        reader.close();
    }

    void skipElements(int... elements) throws XMLStreamException {
        int eventType = reader.getEventType();

        List<Integer> types = asList(elements);
        while (types.contains(eventType))
            eventType = reader.next();
    }
}
29
yves amsellem

これについては、 ユーザーガイド で詳しく説明されています。 http://jaxb.Java.net/ からのJAXBダウンロードには、一度に1つのチャンクを解析する方法の例が含まれています。

ドキュメントが大きい場合、それは通常、ドキュメント内に繰り返しの部分があるためです。おそらく、ラインアイテムのリストが多い発注書であるか、ログエントリが多数あるXMLログファイルである可能性があります。

この種のXMLは、チャンク処理に適しています。主なアイデアは、StAX APIを使用し、ループを実行し、個々のチャンクを個別にアンマーシャリングすることです。プログラムは単一のチャンクに作用し、それを破棄します。このようにして、メモリに保持するチャンクは最大で1つだけになり、大きなドキュメントを処理できるようになります。

これを行う方法の詳細については、JAXBRIディストリビューションのstreaming-unmarshallingの例とpartial-unmarshallingの例を参照してください。ストリーミングアンマーシャリングの例には、任意のネストレベルでチャンクを処理できるという利点がありますが、プッシュモデルを処理する必要があります--- JAXBアンマーシャラーは新しいチャンクを「プッシュ」するため、それらを正しく処理する必要がありますそこ。

対照的に、部分的な非マーシャリングの例はプルモデルで機能します(通常は処理が簡単になります)が、このアプローチには、繰り返し部分以外のデータバインディング部分にいくつかの制限があります。

19
skaffman

Yves Amsellemの答えはかなり良いですが、すべての要素がまったく同じタイプである場合にのみ機能します。そうしないと、アンマーシャルは例外をスローしますが、リーダーはすでにバイトを消費しているため、回復できません。代わりに、Skaffmanのアドバイスに従い、JAXBjarのサンプルを確認する必要があります。

それがどのように機能するかを説明するには:

  1. JAXBアンマーシャラーを作成します。
  2. 適切な要素をインターセプトするために、アンマーシャラーにリスナーを追加します。これは、ArrayListを「ハッキング」して、マーシャリング解除後に要素がメモリに格納されないようにすることで実行されます。
  3. SAXパーサーを作成します。ここでストリーミングが行われます。
  4. Unmarshallerを使用して、SAXパーサーのハンドラーを生成します。
  5. ストリーム!

ソリューションを汎用*に変更しました。ただし、ある程度の反省が必要でした。これで問題が解決しない場合は、JAXBjarのコードサンプルを確認してください。

ArrayListAddInterceptor.Java

import Java.lang.reflect.Field;
import Java.util.ArrayList;

public class ArrayListAddInterceptor<T> extends ArrayList<T> {
    private static final long serialVersionUID = 1L;

    private AddInterceptor<T> interceptor;

    public ArrayListAddInterceptor(AddInterceptor<T> interceptor) {
        this.interceptor = interceptor;
    }

    @Override
    public boolean add(T t) {
        interceptor.intercept(t);
        return false;
    }

    public static interface AddInterceptor<T> {
        public void intercept(T t);
    }

    public static void apply(AddInterceptor<?> interceptor, Object o, String property) {
        try {
            Field field = o.getClass().getDeclaredField(property);
            field.setAccessible(true);
            field.set(o, new ArrayListAddInterceptor(interceptor));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

Main.Java

public class Main {
  public void parsePurchaseOrders(AddInterceptor<PurchaseOrder> interceptor, List<File> files) {
        try {
            // create JAXBContext for the primer.xsd
            JAXBContext context = JAXBContext.newInstance("primer");

            Unmarshaller unmarshaller = context.createUnmarshaller();

            // install the callback on all PurchaseOrders instances
            unmarshaller.setListener(new Unmarshaller.Listener() {
                public void beforeUnmarshal(Object target, Object parent) {
                    if (target instanceof PurchaseOrders) {
                        ArrayListAddInterceptor.apply(interceptor, target, "purchaseOrder");
                    }
                }
            });

            // create a new XML parser
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setNamespaceAware(true);
            XMLReader reader = factory.newSAXParser().getXMLReader();
            reader.setContentHandler(unmarshaller.getUnmarshallerHandler());

            for (File file : files) {
                reader.parse(new InputSource(new FileInputStream(file)));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

*このコードはテストされておらず、説明のみを目的としています。

3
James Watkins