首页 > 解决方案 > 以最少的内存使用将 XML 转换为 Java

问题描述

我希望能够使用尽可能少的内存将大型 XML 文件中的一小部分数据转换为 Java。例如,在下面的代码中,我希望能够从 XML 中提取 id='3' 的文档及其属性,而无需遍历其他文档。我可以单独使用 JAXB 吗?我需要结合使用 XPath 和 JAXB 吗?我应该使用 JAXB MOXy 吗?

<Example id="10" date="1970-01-01" version="1.0">
   <Properties>...</Properties>
   <Summary>...</Summary>
   <Document id="1">...</Document>
   <Document id="2">...</Document>
   <Document id="3">...</Document>
</Example>

标签: javaxmlxpathjaxbmoxy

解决方案


我有一段时间没有使用 JAXB,但是无论选择哪种解决方案,您都需要遍历 XML 文档来读取数据,隐式使用 DOM(然后可能是 XPath)或显式使用 SaX 等流 API(推送模型,您通过回调获取数据)或StAX(拉模型,通过调用方法获取数据)。

JAXB 用户指南在“4.4. 处理大型文档”部分的“4.4.1. 按块处理文档”小节中提供了以下信息。我在 Github 上添加了示例的链接。我这里没有空间来包含所有内容。

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

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

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

和示例看起来也很有希望pull-parserxml-channel我根据pull-parser. 该实现可在Eclipse Implementation of JAXB中找到。

$ cat source.xml
<?xml version="1.0" encoding="UTF-8" ?>
<Example id="10" date="1970-01-01" version="1.0">
   <Properties>properties</Properties>
   <Summary>summary</Summary>
   <Document id="1">one</Document>
   <Document id="2">two</Document>
   <Document id="3">three</Document>
</Example>
$ cat document.xsd
<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="Document">
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base="xs:string">
          <xs:attribute name="id" type="xs:int"/>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>

</xs:schema>
$ cat Main.java
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;
import java.io.FileReader;

import javax.xml.stream.*;
import static javax.xml.stream.XMLStreamConstants.*;
import generated.Document;

public class Main {
  public static void main(String[] args) throws Exception {
    int id = 3;
    JAXBContext jaxbContext = JAXBContext.newInstance("generated");
    Unmarshaller um = jaxbContext.createUnmarshaller();

    XMLInputFactory xmlif = XMLInputFactory.newInstance();
    XMLStreamReader xmlr  = xmlif.createXMLStreamReader(new FileReader("source.xml"));
    int event;
    while (true) {
      event = xmlr.next();
      if (event == END_DOCUMENT) break;
      if (event == START_ELEMENT && xmlr.getName().getLocalPart().equals("Document") && xmlr.getAttributeValue(null, "id").equals("3")) {
        Document document = (Document) um.unmarshal(xmlr);
        System.out.printf("Text is \"%s\"\n", document.getValue());
      }
    }
  }
}
$ java --version
openjdk 15.0.1 2020-10-20
OpenJDK Runtime Environment (build 15.0.1+9-18)
OpenJDK 64-Bit Server VM (build 15.0.1+9-18, mixed mode, sharing)

$ wget https://repo1.maven.org/maven2/com/sun/xml/bind/jaxb-ri/3.0.0/jaxb-ri-3.0.0.zip
$ unzip jaxb-ri-3.0.0.zip
$ export PATH=`pwd`/jaxb-ri/bin:$PATH
$ export CLASSPATH=.:jaxb-ri/mod/*

$ xjc.sh document.xsd
Java major version: 15
parsing a schema...
compiling a schema...
generated/Document.java
generated/ObjectFactory.java

$ javac generated/*.java
$ javac Main.java
$ java Main
Text is "three"

推荐阅读