使用 stax 和 dom 读取大型 XML 文件

Posted

技术标签:

【中文标题】使用 stax 和 dom 读取大型 XML 文件【英文标题】:Reading a big XML file using stax and dom 【发布时间】:2012-03-11 21:35:35 【问题描述】:

我需要读取几个大的 (200Mb-500Mb) XML 文件,所以我想使用 StaX。 我的系统有两个模块 - 一个用于读取文件(使用 StaX);另一个模块(“解析器”模块)假设获取该 XML 的单个条目并使用 DOM 解析它。 我的 XML 文件没有特定的结构 - 所以我不能使用 JaxB。 如何将“解析器”模块传递给我希望它解析的特定条目? 例如:

<Items>
   <Item>
        <name> .... </name>
        <price> ... </price>
   </Item>
   <Item>
        <name> .... </name>
        <price> ... </price>
   </Item>
</Items>

我想使用 StaX 来解析该文件 - 但每个“项目”条目都将传递给“解析器”模块。

编辑: 再读一读之后——我想我需要一个使用流读取 XML 文件的库——但使用 DOM 解析每个条目。有这种事吗?

【问题讨论】:

【参考方案1】:

由于https://bugs.openjdk.java.net/browse/JDK-8016914,Blaise Doughan 的答案在干净的 java 7 和 8 中失败了

java.lang.NullPointerException
at com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl.setXmlVersion(CoreDocumentImpl.java:860)
at com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM.setDocumentInfo(SAX2DOM.java:144)

有趣的是:如果你使用 jaxb unmarshaller,你不会得到 NPE:

package com.common.config;

import java.io.*;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.*;

import org.w3c.dom.*;

public class Demo 


    public static void main(String[] args) throws Exception  
        XMLInputFactory xif = XMLInputFactory.newInstance();
        XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("input.xml"));
        // Advance to root element
        xsr.nextTag(); // TODO: nextTag() can't skip DTD
        xsr.next(); // Advance to first item or EOD

        final JAXBContext jaxbContext = JAXBContext.newInstance();
        final Unmarshaller unm = jaxbContext.createUnmarshaller();
        while(true) 
            // previous unmarshal() already did advance to next element or whitespace
            if (xsr.getEventType() == XMLStreamReader.START_ELEMENT) 
                JAXBElement<Object> jel = unm.unmarshal(xsr, Object.class);
                Node domNode = (Node)jel.getValue();
                System.err.println(domNode.getNodeName());
             else if (!xsr.hasNext()) 
                    break;
             else 
                xsr.next();
            
        
    


原因是:com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXConnector$1 没有实现Locator2 因此它没有getXMLVersion()

【讨论】:

【参考方案2】:

您可以使用 StAX (javax.xml.stream) 解析器并将每个部分转换 (javax.xml.transform) 到 DOM 节点 (org.w3c.dom):

import java.io.*;
import javax.xml.stream.*;
import javax.xml.transform.*;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.dom.DOMResult;
import org.w3c.dom.*

public class Demo 

    public static void main(String[] args) throws Exception  
        XMLInputFactory xif = XMLInputFactory.newInstance();
        XMLStreamReader xsr = xif.createXMLStreamReader(new FileReader("input.xml"));
        xsr.nextTag(); // Advance to statements element

        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer t = tf.newTransformer();
        while(xsr.nextTag() == XMLStreamConstants.START_ELEMENT) 
            DOMResult result = new DOMResult();
            t.transform(new StAXSource(xsr), result);
            Node domNode = result.getNode();
        
    


另见:

Split 1GB Xml file using Java

【讨论】:

谢谢,它对我很有用!我用过它,它对我帮助很大! 对我来说,在 Java 8 中,t.transform() 行抛出了 TransformerException:javax.xml.transform.TransformerException:无法转换 javax.xml.transform 类型的源。 stax.StAXSource. 我将 Apache Xalan 作为依赖项,它提供了自己的 TransformerFactory。解决此问题的一种方法是明确指定 TransformerFactory 类:TransformerFactory transformerFactory = TransformerFactory.newInstance( "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", null );【参考方案3】:

你可以试试 JLibs 的XMLDog。

它使用 SAX 评估 xml 文档上的 xpath(即不将整个 xml 加载到内存中)。 并在节点被命中时返回 dom 节点。

因此,您可以在胖 xml 文档上评估 xpath /Items/Item。在解析每个 Item 节点时,您将收到通知。您可以处理当前的Item dom节点,然后继续。

因此它适用于评估大型文档上的 xpaths

【讨论】:

以上是关于使用 stax 和 dom 读取大型 XML 文件的主要内容,如果未能解决你的问题,请参考以下文章

XML 试题

Java笔试题之《XML部分》

比较流行 java xml解析器有几种 名子是啥?

使用 StAX / Kettle (Pentaho) 读取 XML 文件

stax xml 验证

使用StAX为XML创建索引以便快速访问