【问题标题】:How do I iterate over nodes in a huge XML in a streaming fashion?如何以流方式迭代巨大 XML 中的节点?
【发布时间】:2014-07-03 18:58:30
【问题描述】:

我有一个巨大的 XML 文件,如下所示:

<?xml version="1.0"?>
<catalog>
   <book id="bk101">
      <author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
   </book>
   <book id="bk102">
      <author>Ralls, Kim</author>
      <title>Midnight Rain</title>
   </book>
   [... one gazillion more entries ...]
</catalog>

我想以流的方式迭代这个文件,这样我就不必将整个文件加载到内存中,比如:

InputStream stream = new FileInputStream("gigantic-book-list.xml");
String nodeName = "book";
Iterator it = new StreamingXmlIterator(stream, nodeName);
Document bk101 = it.next();
Document bk102 = it.next();

另外,我希望它可以处理不同的 XML 输入文件,而不必创建特定的对象(例如 Book.java)。

@McDowell 有一个很有前途的方法,它在 https://stackoverflow.com/a/16799693/13365 使用 XMLStreamReaderStreamFilter,但它只提取单个节点。

另外,Camel's .tokenizeXML 完全符合我的要求,所以我想我应该查看源代码。

【问题讨论】:

    标签: java xml stream iterator


    【解决方案1】:
    @XmlRootElement
    public class Book {
      // TODO: getters/setters
      public String author;
      public String title;
    }
    

    假设您想将数据作为强类型对象处理,您可以使用实用程序类型组合 StAX 和 JAXB:

      class ContentFinder implements StreamFilter {
        private boolean capture = false;
    
        @Override
        public boolean accept(XMLStreamReader xml) {
          if (xml.isStartElement() && "book".equals(xml.getLocalName())) {
            capture = true;
          } else if (xml.isEndElement() && "book".equals(xml.getLocalName())) {
            capture = false;
            return true;
          }
          return capture;
        }
      }
    
      class Limiter extends StreamReaderDelegate {
        Limiter(XMLStreamReader xml) {
          super(xml);
        }
    
        @Override
        public boolean hasNext() throws XMLStreamException {
          return !(getParent().isEndElement()
                   && "book".equals(getParent().getLocalName()));
        }
      }
    

    用法:

    XMLInputFactory inFactory = XMLInputFactory.newFactory();
    XMLStreamReader reader = inFactory.createXMLStreamReader(inputStream);
    reader = inFactory.createFilteredReader(reader, new ContentFinder());
    Unmarshaller unmar = JAXBContext.newInstance(Book.class)
        .createUnmarshaller();
    Transformer tformer = TransformerFactory.newInstance().newTransformer();
    while (reader.hasNext()) {
      XMLStreamReader limiter = new Limiter(reader);
      Source src = new StAXSource(limiter);
      DOMResult res = new DOMResult();
      tformer.transform(src, res);
      Book book = (Book) unmar.unmarshal(res.getNode());
      System.out.println(book.title);
    }
    

    【讨论】:

    • 谢谢!可以将其更改为更少的静态类型吗?这样它就可以解析任何 XML 文件,而不需要 Book.java?
    • 当然。您有来自 DOMResult 的 DOM Node,因此您可以遍历它或使用 XPath 查询它。您可以跳过节点生成并直接使用 StAX API 或使用过滤器来构建和处理您认为合适的任何通用数据结构。这里应该有足够的信息来帮助您入门。
    • 绝对 :) 非常感谢。
    • 很好的答案,但遗憾的是这个简单的操作在 2014 年仍然如此丑陋,并且在等待 4 年的 lambdas 之后,Oracle 并没有更新大多数 JDK 库以使用它们。跨度>
    【解决方案2】:

    这不正是SAX API 所达到的吗?

    SAX 解析器比 DOM 样式解析器有一些优势。 SAX 解析器 只需要在每个解析事件发生时报告它,并且通常 一旦报告了几乎所有的信息(它确实, 但是,保留一些东西,例如所有元素的列表 尚未关闭,以便捕获以后的错误,例如 结束标签的顺序错误)。因此,一个所需的最小内存 SAX 解析器与 XML 文件的最大深度成正比(即, XML 树的)和单个 XML 事件中涉及的最大数据 (例如单个开始标签的名称和属性,或内容 处理指令等)。

    我认为您需要简单地跟踪每本书的startElement() 调用,并记录从那里传入的元素/属性。在收到相应的endElement() 呼叫后处理。请记住,characters() 可以跨同一个文本节点多次调用。

    【讨论】:

    • 我可能可以,但据我所知,我必须处理每个节点中的每种事件类型,并将它们重建到我将在 next() 事件中返回的节点/文档中。跨度>
    • 真的。您必须处理每个元素。是重新构建成一个新的 XML 文档,还是(比如说)动态地构建一个代表对象(Book.java),这取决于您。我可能会做后者,并在您解析时转移出 XML 域
    • 我在写问题时并不清楚这一点,但我需要将其用作通用 xml 拆分器。我将无法解组到特定的类。
    【解决方案3】:

    然后使用 SAX 解析器。检查SAX parser tutorial from Oracle

    【讨论】:

      【解决方案4】:

      您需要描述您的流程的期望输出是什么,以及您的技术限制是什么。

      XSLT 3.0 中的流式处理仍然是最前沿的,但是可以很容易地表达许多转换。例如,使用 Saxon-EE 9.5,您可以将流式转换中书籍的平均价格计算为

      <xsl:template name="main">
        <xsl:stream href="books.xml">
          <xsl:value-of select="avg(/books/book/price)"/>
        </xsl:stream>
      </xsl:template>
      

      【讨论】:

      • 但我确实描述了它。看看我链接到的例子。它将单个 节点提取到文档中。我想对多个节点重复执行此操作。
      • 抱歉,在我开始编写代码之前,我要求提供比这更好的规范。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-03
      • 1970-01-01
      • 2012-01-21
      • 2020-08-10
      • 2015-01-05
      • 2011-06-01
      相关资源
      最近更新 更多