JAXB能否以块的形式解析大型XML文件

时间:2009-07-15 21:26:25

标签: java jaxb

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

4 个答案:

答案 0 :(得分:26)

因为代码很重要,所以这里有一个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();
    }
}

答案 1 :(得分:18)

user guide详细说明了这一点。来自http://jaxb.java.net/的JAXB下载包含一个如何一次解析一个块的示例。

  

当文档很大时,它就是   通常因为有重复性   其中的部分。也许这是一次购买   订单包含大量订单项,   或者它可能是一个XML日志文件   大量的日志条目。

     

这种XML适合   块处理;主要的想法是   使用StAX API,运行循环,和   unmarshal个别块   分别。你的程序作用于   单块,然后把它扔掉。   通过这种方式,你只会保持   内存中最多的一个块,允许   你要处理大型文件。

     

请参阅streaming-unmarshalling   例子和部分解组   JAXB RI发行版中的示例   有关如何执行此操作的更多信息。该   streaming-unmarshalling示例有一个   它可以处理块的优点   任意巢级别,但它需要   你要处理推模型---   JAXB unmarshaller将“推动”新的   大块给你,你需要   在那里处理它们。

     相反,部分解组   示例适用于拉模型(其中   通常使处理更容易),   但这种方法有一些局限性   在数据绑定部分以外的   重复的部分。

答案 2 :(得分:2)

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

解释它的工作原理:

  1. 创建一个JAXB unmarshaller。
  2. 向unmarshaller添加一个侦听器,用于拦截相应的元素。这是通过“黑客”ArrayList来完成的,以确保元素在被解组后不会存储在内存中。
  3. 创建SAX解析器。这就是流媒体发生的地方。
  4. 使用unmarshaller为SAX解析器生成处理程序。
  5. 流<!/ LI>

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

    *此代码尚未经过测试,仅供说明之用。

答案 3 :(得分:1)

我写了small library(在Maven Central上提供)来帮助读取大的XML文件并按块处理它们。请注意,它仅适用于具有唯一容器的文件,该容器具有数据列表(即使来自不同类型)。换句话说,您的文件必须遵循以下结构:

<container>
   <type1>...</type1>
   <type2>...</type2>
   <type1>...</type1>
   ...
</container>

下面是一个示例,其中Type1Type2,...是文件中重复数据的JAXB表示形式:

try (StreamingUnmarshaller unmarshaller = new StreamingUnmarshaller(Type1.class, Type2.class, ...)) {
    unmarshaller.open(new FileInputStream(fileName));
    unmarshaller.iterate((type, element) -> doWhatYouWant(element));
}

您可以在库的GitHub页面上找到带有详细示例的更多信息。