25

我需要解析可能很大的 XML 文件,其中的模式已经在几个 XSD 文件中提供给我,因此 XML 绑定非常受欢迎。我想知道是否可以使用 JAXB 分块解析文件,如果可以,如何解析。

4

4 回答 4

32

因为代码很重要,所以这里有一个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();
    }
}
于 2012-02-13T11:53:10.697 回答
20

这在用户指南中有详细说明。从http://jaxb.java.net/下载的 JAXB包含一个如何一次解析一个块的示例。

当文档很大时,通常是因为其中有重复的部分。可能是包含大量行项目的采购订单,或者可能是包含大量日志条目的 XML 日志文件。

这种 XML 适用于块处理;主要思想是使用 StAX API,运行一个循环,并分别解组各个块。您的程序作用于单个块,然后将其丢弃。这样,您最多只能在内存中保留一个块,这使您可以处理大型文档。

有关如何执行此操作的更多信息,请参阅 JAXB RI 分发中的流式解组示例和部分解组示例。流式解组示例的优点是它可以处理任意嵌套级别的块,但它需要您处理推送模型 --- JAXB 解组器会将新块“推送”给您,您需要正确处理它们那里。

相比之下,部分解组示例在拉模型中工作(这通常使处理更容易),但这种方法在数据绑定部分(重复部分除外)方面存在一些限制。

于 2009-07-15T21:29:32.787 回答
3

Yves Amsellem 的答案非常好,但只有在所有元素的类型完全相同时才有效。否则你的 unmarshall 会抛出一个异常,但是阅读器已经消耗了字节,所以你将无法恢复。相反,我们应该遵循 Skaffman 的建议并查看 JAXB jar 中的示例。

解释它是如何工作的:

  1. 创建一个 JAXB 解组器。
  2. 向解组器添加一个侦听器以拦截适当的元素。这是通过“破解” ArrayList 来完成的,以确保元素在解组后不会存储在内存中。
  3. 创建一个 SAX 解析器。这就是流式传输发生的地方。
  4. 使用解组器为 SAX 解析器生成处理程序。
  5. 溪流!

我将解决方案修改为通用*。然而,它需要一些反思。如果这不正确,请查看 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);
        }
    }

}

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

*此代码未经测试,仅用于说明目的。

于 2015-10-10T17:14:43.733 回答
2

我编写了一个小型库(可在 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 页面上找到包含详细示例的更多信息。

于 2020-09-28T00:15:49.133 回答