Может ли JAXB анализировать большие XML-файлы по частям
Мне нужно проанализировать потенциально большие XML-файлы, схема которых уже предоставлена мне в нескольких XSD-файлах, поэтому привязка к XML очень предпочтительна. Я хотел бы знать, могу ли я использовать JAXB для разбора файла на куски, и если да, то как.
3 ответа
Поскольку код имеет значение, вот 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();
}
}
Это подробно описано в руководстве пользователя. Загрузка JAXB с http://jaxb.java.net/ содержит пример того, как анализировать один блок за раз.
Когда документ большой, это обычно потому, что в нем есть повторяющиеся части. Возможно, это заказ на покупку с большим списком позиций, или, возможно, это файл журнала XML с большим количеством записей журнала.
Этот вид XML подходит для чанк-обработки; Основная идея состоит в том, чтобы использовать API StAX, запустить цикл и разархивировать отдельные фрагменты по отдельности. Ваша программа действует на один кусок, а затем выбрасывает его. Таким образом, вы будете хранить не более одного фрагмента в памяти, что позволяет обрабатывать большие документы.
См. Пример потоковой отмены маршалинга и пример частичной отмены маршалинга в дистрибутиве RI JAXB для получения дополнительной информации о том, как это сделать. Преимущество примера потоковой отмены маршрутизации состоит в том, что он может обрабатывать фрагменты на произвольном уровне вложенности, но для этого требуется, чтобы вы работали с моделью push - JAXB unmarshaller "протолкнет" новую порцию вам, и вам нужно будет правильно обработать их там.
Напротив, пример с частичным демаршированием работает в модели извлечения (которая обычно облегчает обработку), но у этого подхода есть некоторые ограничения в частях привязки данных, отличных от повторяющейся части.
Ответ Ива Амселлема довольно хороший, но работает, только если все элементы имеют одинаковый тип. В противном случае ваш unmarshall сгенерирует исключение, но читатель уже использует байты, поэтому вы не сможете восстановить. Вместо этого мы должны следовать совету Скаффмана и посмотреть на образец из банки JAXB.
Чтобы объяснить, как это работает:
- Создайте демаршаллер JAXB.
- Добавьте слушателя к unmarshaller для перехвата соответствующих элементов. Это делается путем "взлома" ArrayList, чтобы гарантировать, что элементы не будут сохранены в памяти после того, как они были распакованы.
- Создать SAX-парсер. Вот где происходит потоковая передача.
- Используйте unmarshaller для генерации обработчика для синтаксического анализатора SAX.
- Поток!
Я изменил решение, чтобы оно было общим *. Однако это потребовало некоторых размышлений. Если это не в порядке, пожалуйста, посмотрите примеры кода в JAXB-банках.
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);
}
}
}
* Этот код не был проверен и предназначен только для иллюстративных целей.
Я написал небольшую библиотеку (доступную на Maven Central), чтобы помочь читать большие файлы XML и обрабатывать их по частям. Обратите внимание, что его можно применить только к файлам с уникальным контейнером, имеющим список данных (даже из разных типов). Другими словами, ваш файл должен соответствовать структуре:
<container>
<type1>...</type1>
<type2>...</type2>
<type1>...</type1>
...
</container>
Вот пример, где
Type1
,
Type2
,... являются JAXB-представлением повторяющихся данных в файле:
try (StreamingUnmarshaller unmarshaller = new StreamingUnmarshaller(Type1.class, Type2.class, ...)) {
unmarshaller.open(new FileInputStream(fileName));
unmarshaller.iterate((type, element) -> doWhatYouWant(element));
}
Вы можете найти дополнительную информацию с подробными примерами на странице библиотеки GitHub.