streaming api

    技术2024-04-05  83

    StAX概述

    自成立以来,用于XML处理的Java API(JAXP)提供了两种处理XML的方法-文档对象模型(DOM)方法(使用标准对象模型表示XML文档)和XML简单API(SAX)方法,该方法使用应用程序提供的事件处理程序来处理XML。 在JSR-173:XML的流API(StAX)中提出了这些方法的流替代方法。 它的最终版本于2004年3月发布,并成为JAXP 1.4的一部分(将包含在即将发布的Java 6版本中)。

    顾名思义,StAX注重流媒体 。 实际上,StAX与其他方法的不同之处在于应用程序将XML作为事件流进行处理的能力。 将XML作为一组事件进行处理的想法并不是全新的(实际上,它已经存在于SAX中)。 但是,不同之处在于StAX允许应用程序代码一个接一个地拉出这些事件,而不必提供在解析器方便时从解析器接收事件的处理程序。

    StAX实际上由两组XML处理API组成,每组提供不同的抽象级别。 基于游标的API允许应用程序将XML作为令牌(或事件)流使用。 应用程序可以检查解析器的状态并获取有关最后解析的令牌的信息,然后前进到下一个令牌,依此类推。 这是一个相当底层的API。 尽管效率很高,但它没有提供底层XML结构的抽象。 基于高级迭代器的API允许应用程序将XML作为一系列事件对象进行处理,每个事件对象都将XML结构的一部分传达给应用程序。 应用程序所需要做的就是确定已解析事件的类型,将其转换为相应的具体类型,并使用其方法来获取与该事件有关的信息。

    基础

    为了使用这两个API,应用程序必须首先获得一个具体的XMLInputFactory 。 在经典的JAXP风格中,这是使用Abstract Factory模式完成的。 XMLInputFactory类提供静态的newInstance方法,这些方法用于定位和实例化具体工厂。 要配置此实例,可以设置自定义或预定义的属性(其名称在类XMLInputFactory中定义)。 最后,为了使用基于游标的API,应用程序通过调用createXMLStreamReader方法之一来获取XMLStreamReader 。 另外,为了使用基于事件迭代器的API,应用程序将调用createXMLEventReader方法之一来获取XMLEventReader (请参见清单1)。

    清单1.获取和配置默认的XMLInputFactory
    // get the default factory instance XMLInputFactory factory = XMLInputFactory.newInstance(); // configure it to create readers that coalesce adjacent character sections factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); XMLStreamReader r = factory.createXMLStreamReader(input); // ...

    XMLStreamReader和XMLEventReader允许应用程序自行遍历基础XML流。 两种方法之间的区别在于它们如何显示已解析的XML InfoSet的片段。 XMLStreamReader充当游标,其指向刚超出最近解析的XML令牌之外的位置,并提供获取有关此令牌的更多信息的方法。 这种方法非常节省内存,因为它不会创建任何新对象。 但是,业务应用程序开发人员可能会发现XMLEventReader稍微更直观,因为它实际上是将XML转换为事件对象流的标准Java迭代器。 每个事件对象依次封装与其表示的特定XML结构有关的信息。 本系列的第2部分将详细介绍基于事件迭代器的API。

    至于使用哪种API样式取决于情况。 与基于游标的API相比,基于事件迭代器的API代表了一种更加面向对象的方法。 这样,由于当前解析器的状态反映在事件对象中,因此更容易应用于模块化体系结构。 因此,应用程序组件在处理事件时不需要访问解析器/读取器。 此外,可以使用XMLInputFactory的createXMLEventReader(XMLStreamReader)方法从XMLStreamReader创建XMLEventReader 。

    StAX还定义了序列化API,这是Java的标准XML处理支持中非常缺少的功能。 与其解析对象一样,它也是一种流API,有两种形式:用于令牌的较低级XMLStreamWriter和用于事件对象的较高级XMLEventWriter 。 XMLStreamWriter提供了用于编写单个XML令牌(例如,打开和关闭标签或元素属性)的方法,而无需检查其格式是否正确。 另一方面, XMLEventWriter允许应用程序将完整的XML事件对象添加到输出中。 在第3部分中,您将详细探讨StAX序列化API。

    为什么要使用StAX?

    在致力于学习新的XML处理API之前,您可能想知道是否值得这样做。 实际上,StAX采用的基于拉的方法相对于其他方法具有几个重要的优点。 首先,无论使用哪种API样式,都是由应用程序调用读取器(解析器),而不是相反。 通过保留对解析过程的控制,您可以简化调用代码以精确处理其期望的内容,并选择在遇到意外情况时仅停止解析。 此外,由于此方法不是基于处理程序回调,因此应用程序不需要像使用SAX时那样需要维护模拟的解析器状态。

    StAX还保留了SAX通过DOM提供的优势。 通过将焦点从结果对象模型转移到已解析的流本身,应用程序可以处理理论上无限的XML流,因为事件本质上是瞬态的,不需要在内存中累积。 对于使用XML作为消息传递协议而不是表示文档内容的一类应用程序,例如Web服务或即时消息传递应用程序,这尤其重要。 例如,如果将传递给DOM的Web服务路由器servlet所做的只是将其转换为特定于应用程序的对象模型,然后简单地将其丢弃,则几乎没有用。 使用StAX直接进入应用程序模型更为有效。 对于可扩展消息和状态协议(XMPP)客户端,使用DOM是绝对不可能的-XMPP客户端/服务器流是根据用户输入的消息实时递增生成的。 等待流的结束标记(以最终完成DOM的构建)意味着等待直到对话结束。 通过将XML处理为一系列事件,应用程序可以以最合适的方式对每个事件做出React(例如,显示传入的即时消息等)。

    由于其双向特性,StAX还非常好地支持链式处理,尤其是在事件级别。 接受事件(来自任何来源)的能力封装在XMLEventConsumer接口中,该接口由XMLEventWriter扩展。 因此,您可以模块化地编写应用程序,以从XMLEventReader(它也是一个普通的Iterator,可以将其视为此类)读取XML事件,对其进行处理,然后将其传递给事件使用者(如果可以,则进一步扩展处理链)需要)。 正如您将在第2部分中学习的那样,还可以通过使用应用程序提供的过滤器(实现EventFilter接口的类)或使用EventReaderDelegate装饰现有的XMLEventReader来定制XMLEventReader。

    总而言之,StAX使应用程序比DOM或SAX更接近底层XML。 通过使用StAX,应用程序不仅可以建立所需的对象模型(而不必处理标准DOM),而且还可以方便地做到这一点,而不是仅在从解析器获得回调之后才可以这样做。

    下一节将详细研究基于游标的API以及如何使用它来有效处理XML流。

    基于游标的API

    使用基于游标的API时,应用程序通过在XML令牌流上推进逻辑游标来处理XML。 基于游标的解析器实质上是一种状态机,由于事件而从一种状态良好的状态转换为另一种状态。 在这种情况下,触发事件是XML令牌,当应用程序使用适当的方法沿令牌流推进解析器时,将对其进行解析。 在每种状态下,您可以使用一组方法来获取有关最新事件的信息。 通常,并非所有方法都适用于所有状态。

    要使用基于游标的方法,应用程序必须首先通过调用XMLInputFactory的createXMLStreamReader方法之一从XMLInputFactory获得XMLStreamReader 。 此方法有多个版本,每个版本支持不同类型的输入。 例如,可以创建XMLStreamReader来解析plain java.io.InputStream , java.io.Reader ,还可以解析JAXP Source( javax.xml.transform.Source )。 从理论上讲,最后一个选项应该使与其他JAXP技术(例如SAX和DOM)的交互更加容易。

    清单2.创建一个XMLStreamReader来解析InputStream
    URL url = new URL(uri); InputStream input = url.openStream(); XMLInputFactory factory = XMLInputFactory.newInstance(); XMLStreamReader r = factory.createXMLStreamReader(uri, input); // process the stream // ... r.close(); input.close();

    XMLStreamReader接口实质上定义了基于游标的API(尽管令牌常量是在其超类型接口XMLStreamConstants中定义的)。 之所以称为基于游标,是因为读取器充当基础令牌流上的游标。 应用程序可以使光标沿着令牌流向前移动,并在光标位置检查令牌。

    XMLStreamReader提供了几种导航令牌流的方法。 为了确定光标当前指向的令牌(或事件)的类型,应用程序可以调用getEventType() 。 此方法返回接口XMLStreamConstants定义的标记常量之一。 要移至下一个令牌,应用程序可以调用next() 。 此方法还返回已解析标记的类型-与后续调用getEventType()所返回的值相同。 只要方法hasNext()返回true(即,有更多要解析的标记),就只能调用此方法(以及其他推进读者的方法)。

    清单3.使用XMLStreamReader处理XML的常用模式
    // create an XMLStreamReader XMLStreamReader r = ...; try { int event = r.getEventType(); while (true) { switch (event) { case XMLStreamConstants.START_DOCUMENT: // add cases for each event of interest // ... } if (!r.hasNext()) break; event = r.next(); } } finally { r.close(); }

    其他一些方法可能会使reader前进。 方法nextTag()将跳过任何空格,注释或处理指令,直到到达START_ELEMENT或END_ELEMENT 。 当解析纯元素内容时,此方法很有用。 如果在找到标签之前(注释或处理指令除外)遇到非空白文本,则会引发异常。 方法getElementText()将返回元素的开始和结束标记之间(即START_ELEMENT和END_ELEMENT之间getElementText()所有文本内容。 如果找到任何嵌套的元素,它将引发异常。

    您会注意到,在此上下文中,术语“令牌”和“事件”可互换使用。 虽然基于游标的API的文档讨论事件,但将输入源视为令牌流更容易。 由于还有其他基于事件的API样式(事件是适当的对象),因此它也不太混乱。 但是, XMLStreamReader的事件本身并不是全部标记。 例如, START_DOCUMENT和END_DOCUMENT事件不需要匹配的令牌。 前一个事件在解析开始之前发生,而后一个事件在无法进行进一步解析之后发生(例如,在解析了最后一个元素的结束标记之后,读取器处于END_ELEMENT状态;但是,在尝试解析更多令牌并没有找到任何令牌之后,阅读器将转换为END_DOCUMENT状态)。

    处理XML文档

    在每个解析器状态下,应用程序都可以使用适用的方法来获取有关它的信息。 例如,方法getNamespaceContext()和getNamespaceURI()可以分别获取当前名称空间上下文和当前有效的名称空间URI,而与当前事件类型无关。 同样, getLocation()可以获取有关当前事件位置的信息。 方法hasName()和hasText()可以分别确定当前事件是否具有名称(例如元素或属性)或文本(例如字符,注释或CDATA)。 方法isStartElement() , isEndElement() , isCharacters()和isWhiteSpace()是确定当前事件性质的便捷快捷方式。 最后,方法require( int , String , String )可以声明预期的解析器状态; 除非当前事件是指定的类型,并且本地名称和名称空间(如果指定)与当前事件匹配,则它将引发异常。

    清单4.使用当前事件为START_ELEMENT时可用的属性相关方法
    if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { System.out.println("Start Element: " + reader.getName()); for(int i = 0, n = reader.getAttributeCount(); i < n; ++i) { QName name = reader.getAttributeName(i); String value = reader.getAttributeValue(i); System.out.println("Attribute: " + name + "=" + value); } }

    XMLStreamReader创建后,立即以START_DOCUMENT状态启动(即, getEventType()将返回START_DOCUMENT )。 处理令牌时,请考虑到这一点。 与迭代器不同,游标无需先前进(使用next() )即可进入有效状态。 同样,在读者转换到其最终状态END_DOCUMENT之后,应用程序不应尝试前进。 一旦处于此状态,方法hasNext()将返回false。

    START_DOCUMENT事件提供了获取有关文档本身的信息的方法,例如getEncoding() , getVersion()和isStandalone() 。 应用程序还可以通过调用getProperty(String)获得命名的属性值; 但是,某些属性仅在特定状态下定义(例如,如果当前事件为DTD,则属性javax.xml.stream.notations和javax.xml.stream.entities返回任何符号和实体声明)。

    在START_ELEMENT和END_ELEMENT ,可以使用与元素名称和名称空间相关的方法(例如getName() , getLocalName() , getPrefix()和getNamespaceXXX() ); 与属性相关的方法( getAttributeXXX() )也可以在START_ELEMENT 。

    ATTRIBUTE和NAMESPACE也被视为独立事件,尽管在解析典型的XML文档时不会遇到它们。 但是,由于XPath查询而返回ATTRIBUTE或NAMESPACE节点时,可能会遇到它们。

    在基于文本的事件(例如CHARACTERS , CDATA , COMMENT和SPACE )中,请使用各种getTextXXX()方法获取文本。 您可以分别使用getPITarget()和getPIData()来检索PROCESSING_INSTRUCTION的目标和数据。 ENTITY_REFERENCE和DTD也支持getText() ; ENTITY_REFERENCE也getLocalName() 。

    解析完成后,应用程序将关闭阅读器以释放其在此过程中获取的任何资源。 请注意,这不会关闭基础输入源。

    清单5提供了使用基于游标的API处理XML文档的完整示例。 首先,获取XMLInputFactory的默认实例,并创建一个XMLStreamReader来解析给定的输入流。 接下来,迭代检查读者的状态,并根据当前事件类型,报告特定信息(例如,如果处于START_ELEMENT状态,则为元素名称及其属性)。 最后,到达END_DOCUMENT时关闭阅读器。

    清单5.使用XMLStreamReader解析XML文档的完整示例
    XMLInputFactory factory = XMLInputFactory.newInstance(); XMLStreamReader r = factory.createXMLStreamReader(input); try { int event = r.getEventType(); while (true) { switch (event) { case XMLStreamConstants.START_DOCUMENT: out.println("Start Document."); break; case XMLStreamConstants.START_ELEMENT: out.println("Start Element: " + r.getName()); for(int i = 0, n = r.getAttributeCount(); i < n; ++i) out.println("Attribute: " + r.getAttributeName(i) + "=" + r.getAttributeValue(i)); break; case XMLStreamConstants.CHARACTERS: if (r.isWhiteSpace()) break; out.println("Text: " + r.getText()); break; case XMLStreamConstants.END_ELEMENT: out.println("End Element:" + r.getName()); break; case XMLStreamConstants.END_DOCUMENT: out.println("End Document."); break; } if (!r.hasNext()) break; event = r.next(); } } finally { r.close(); }

    XMLStreamReader高级用法

    也可以通过使用基本阅读器和应用程序定义的过滤器(即,实现StreamFilter的类的实例)调用XMLInputFactory的createFilteredReader方法来创建过滤的XMLStreamReader 。 在浏览过滤的阅读器时,只要基本阅读器前进到下一个标记,便会查询过滤器。 如果过滤器批准当前事件,则将其暴露给过滤后的阅读器。 如果不是,则跳过令牌并测试下一个令牌,依此类推。 这种方法允许开发人员创建基于游标的XML处理器,以处理解析后的内容的简化子集,并将其与各种扩展内容模型的过滤器结合使用。

    要执行更复杂的流操作,请子类StreamReaderDelegate并重写适当的方法。 然后可以使用此子类的实例包装基本XMLStreamReader ,从而为应用程序提供基本XML流的修改视图。 使用此技术可以对XML流执行简单的转换,例如过滤或替换某些令牌,甚至用新的令牌扩充该流。

    在清单6中,使用自定义StreamReaderDelegate包装了一个基本XMLStreamReader ,并重写了它的next()方法以跳过COMMENT和PROCESSING_INSTRUCTION事件。 使用生成的阅读器时,应用程序不必担心会遇到这些类型的令牌。

    清单6.使用定制的StreamReaderDelegate过滤掉注释和处理指令
    URL url = new URL(uri); InputStream input = url.openStream(); XMLInputFactory f = XMLInputFactory.newInstance(); XMLStreamReader r = f.createXMLStreamReader(uri, input); XMLStreamReader fr = new StreamReaderDelegate(r) { public int next() throws XMLStreamException { while (true) { int event = super.next(); switch (event) { case XMLStreamConstants.COMMENT: case XMLStreamConstants.PROCESSING_INSTRUCTION: continue; default: return event; } } } }; try { int event = fr.getEventType(); while (true) { switch (event) { case XMLStreamConstants.COMMENT: case XMLStreamConstants.PROCESSING_INSTRUCTION: // this should never happen throw new IllegalStateException("Filter failed!"); default: // process XML normally } if (!fr.hasNext()) break; event = fr.next(); } } finally { fr.close(); } input.close();

    超越基于游标的处理

    如您所见,基于游标的API完全是关于效率的。 所有状态信息都可以直接从流读取器中获得,并且不会创建额外的对象。 这在性能和低内存占用非常重要的应用中特别有用。

    基于拉式XML解析的好处已经有一段时间了。 实际上,StAX本身是从一种称为XML Pull Parsing的方法派生的。 XML Pull Parser API与StAX提供的基于光标的API相似。 可以检查解析器状态以获取有关上一个已解析事件的信息,然后前进到下一个事件,依此类推。 没有提供基于事件迭代器的替代API。 这种方法非常轻巧,特别适合于资源受限的环境,例如J2ME。 但是,很少有实现提供诸如验证之类的企业级功能,因此XML Pull在企业Java开发人员中从未流行。

    基于以前的拉式解析器实现的经验,StAX的创建者选择包括基于对象的替代基于游标的API。 即使XMLEventReader接口在表面上看似简单,但基于事件迭代器的方法仍比基于游标的方法具有重要的优势。 通过将解析器事件转换为一流的对象,它允许应用程序以面向对象的方式处理它们。 这样可以在多个应用程序组件之间实现更好的模块化和代码重用。

    清单7.使用StAX XMLEventReader解析XML
    XMLInputFactory inputFactory = XMLInputFactory.newInstance(); XMLEventReader reader = inputFactory.createXMLEventReader(input); try { while (reader.hasNext()) { XMLEvent e = reader.nextEvent(); if (e.isCharacters() && ((Characters) e).isWhiteSpace()) continue; out.println(e); } } finally { reader.close(); }

    摘要

    在本文中,向您介绍了StAX及其较低级别的基于游标的API。 第2部分将对事件迭代器API进行更深入的研究。


    翻译自: https://www.ibm.com/developerworks/java/library/x-stax1/index.html

    相关资源:jdk-8u281-windows-x64.exe
    Processed: 0.012, SQL: 9