スキーマがすでにいくつかの XSD ファイルで提供されている、潜在的に大きな XML ファイルを解析する必要があるため、XML バインディングが非常に好まれます。JAXB を使用してファイルをチャンクで解析できるかどうか、また可能であればその方法を知りたいです。
4 に答える
コードが重要であるため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();
}
}
これについては、ユーザー ガイドに詳しく説明されています。http://jaxb.java.net/からダウンロードした JAXBには、一度に 1 つのチャンクを解析する方法の例が含まれています。
ドキュメントが大きい場合、それは通常、反復部分があるためです。大量の品目リストを含む発注書である可能性や、多数のログ エントリを含む XML ログ ファイルである可能性があります。
この種の XML は、チャンク処理に適しています。主なアイデアは、StAX API を使用し、ループを実行し、個々のチャンクを個別に非整列化することです。プログラムは単一のチャンクで動作し、それを破棄します。この方法では、メモリ内に最大 1 つのチャンクしか保持できず、大きなドキュメントを処理できます。
これを行う方法の詳細については、JAXB RI ディストリビューションのストリーミング非整列化の例と部分非整列化の例を参照してください。ストリーミングアンマーシャリングの例には、任意のネスト レベルでチャンクを処理できるという利点がありますが、プッシュ モデルを処理する必要があります --- JAXB アンマーシャラーは新しいチャンクを「プッシュ」し、それらを正しく処理する必要があります。そこの。
対照的に、部分的なアンマーシャリングの例はプル モデルで機能します (通常は処理が簡単になります) が、このアプローチには、繰り返される部分以外のデータ バインディング部分にいくつかの制限があります。
Yves Amsellem の答えはかなり良いですが、すべての要素がまったく同じ型である場合にのみ機能します。そうしないと、アンマーシャルで例外がスローされますが、リーダーは既にバイトを消費しているため、回復できません。代わりに、Skaffman のアドバイスに従い、JAXB jar のサンプルを確認する必要があります。
それがどのように機能するかを説明するには:
- JAXB アンマーシャラーを作成します。
- 適切な要素をインターセプトするために、アンマーシャラーにリスナーを追加します。これは、ArrayList を「ハッキング」して、アンマーシャリング後に要素がメモリに格納されないようにすることによって行われます。
- SAX パーサーを作成します。ここでストリーミングが行われます。
- アンマーシャラーを使用して、SAX パーサーのハンドラーを生成します。
- ストリーム!
ソリューションを一般的なものに変更しました*。ただし、反省が必要でした。これで問題が解決しない場合は、JAXB jar のコード サンプルを参照してください。
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);
}
}
}
*このコードはテストされておらず、説明のみを目的としています。