如何提高 Java 中 StAX xml 解析器的速度?

kan*_*004 3 java xml stax

我有一个 XML 解析器StAX,我正在使用它来解析一个巨大的文件。但是,我想尽可能缩短时间。我正在读取将其放入数组中的值并将其发送到另一个函数进行评估。我正在调用该displayName标签,它应该在获取名称后立即转到下一个 xml,而不是读取整个 xml 文件。我正在寻找最快的方法。

爪哇:


import java.io.File;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Iterator;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.*;

public class Driver {

    private static boolean bname;

    public static void main(String[] args) throws FileNotFoundException, XMLStreamException {

        File file = new File("C:\\Users\\Robert\\Desktop\\root\\SDKCode\\src\\main\\java\\com\\example\\xmlClass\\data.xml");


        parser(file);
    }

    public static void parser(File file) throws FileNotFoundException, XMLStreamException {

        bname = false;


        XMLInputFactory factory = XMLInputFactory.newInstance();


        XMLEventReader eventReader = factory.createXMLEventReader(new FileReader(file));


        while (eventReader.hasNext()) {

            XMLEvent event = eventReader.nextEvent();

            // This will trigger when the tag is of type <...>
            if (event.isStartElement()) {
                StartElement element = (StartElement) event;


                Iterator<Attribute> iterator = element.getAttributes();
                while (iterator.hasNext()) {
                    Attribute attribute = iterator.next();
                    QName name = attribute.getName();
                    String value = attribute.getValue();
                    System.out.println(name + " = " + value);
                }


                if (element.getName().toString().equalsIgnoreCase("displayName")) {
                    bname = true;
                }

            }


            if (event.isEndElement()) {
                EndElement element = (EndElement) event;


                if (element.getName().toString().equalsIgnoreCase("displayName")) {
                    bname = false;
                }


            }


            if (event.isCharacters()) {
                // Depending upon the tag opened the data is retrieved .
                Characters element = (Characters) event;

                if (bname) {
                    System.out.println(element.getData());
                }

            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

XML:

<?xml version="1.0" encoding="UTF-8"?>
<results
        xmlns="urn:www-collation-com:1.0"
        xmlns:coll="urn:www-collation-com:1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:www-collation-com:1.0
              urn:www-collation-com:1.0/results.xsd">

    <WebServiceImpl array="1"
        guid="FFVVRJ5618KJRHNFUIRV845NRUVHR" xsi:type="coll:com.model.topology.app.web.WebService">
        <isPlaceholder>false</isPlaceholder>
        <displayName>server.servername1.siqom.siqom.us.com</displayName>
        <hierarchyType>WebService</hierarchyType>
        <hierarchyDomain>app.web</hierarchyDomain>
    </WebServiceImpl>
</results>

<?xml version="1.0" encoding="UTF-8"?>
<results
        xmlns="urn:www-collation-com:1.0"
        xmlns:coll="urn:www-collation-com:1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:www-collation-com:1.0
              urn:www-collation-com:1.0/results.xsd">

    <WebServiceImpl array="1"
        guid="FFVVRJ5618KJRHNFUIRV845NRUVHR" xsi:type="coll:com.model.topology.app.web.WebService">
        <isPlaceholder>false</isPlaceholder>
        <displayName>server.servername2.siqom.siqom.us.com</displayName>
        <hierarchyType>WebService</hierarchyType>
        <hierarchyDomain>app.web</hierarchyDomain>
    </WebServiceImpl>
</results>

<?xml version="1.0" encoding="UTF-8"?>
<results
        xmlns="urn:www-collation-com:1.0"
        xmlns:coll="urn:www-collation-com:1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:www-collation-com:1.0
              urn:www-collation-com:1.0/results.xsd">

    <WebServiceImpl array="1"
        guid="FFVVRJ5618KJRHNFUIRV845NRUVHR" xsi:type="coll:com.model.topology.app.web.WebService">
        <isPlaceholder>false</isPlaceholder>
        <displayName>server.servername3.siqom.siqom.us.com</displayName>
        <hierarchyType>WebService</hierarchyType>
        <hierarchyDomain>app.web</hierarchyDomain>
    </WebServiceImpl>
</results>


etc...

Run Code Online (Sandbox Code Playgroud)

GPI*_*GPI 8

未来有几种方法。

分割文件

首先,如果您的大文件实际上是几个串联的 XML 文件(如您所示的示例),那么这个大文件不是(有效的)XML 文件,我建议在处理严格的 XML 解析库(Stax、DOM、 SAX、XSL,无论什么......)。

有效的 XML 文件只有一个序言和一个根元素。

您可以使用 XML 序言作为分割标记,使用纯 IO/字节级 API(不涉及 XML)。

然后,每个分割都可以被视为单个 XML“文件”(如果需要,可以独立进行处理,以实现多线程目的)。我并不是说“文件”,它可能是byte[]从原始“大文件”中分割出来的一大块。

加速 XML 解析

关于你的代码

使用XMLEventReader,您的示例代码中有一些内容很突出。

  1. 您不应该像现在这样迭代属性之一。除非我遗漏了什么,否则你不会在这次迭代中做任何事情。
  2. 一旦你到达START_ELEMENTwho localNameis displayName,你应该调用getElementText,它是解析器内部的,有一些 while 循环无法实现的速度优化技巧。此调用将使读者停留在匹配的位置END_ELEMENT,因此实际上,您大大简化了代码(仅检查displayName START_ELEMENT,仅此而已)。
  3. 您的 XML 看起来格式良好,因此一旦找到结果就可以跳过解析
  4. XMLInputFactories旨在重复使用,因此不要为每个文件创建一个,而是创建一个共享实例。
  5. XML(xxx)Reader是可关闭的,所以关闭它们。
  6. 一些 XML 库具有比 JDK 提供的更快的字符解码方案(了解 XML 编码的内部允许它们这样做),因此,如果您有一个有效的 XML prolog 在文件开头对编码进行了解析,那么您应该为您的工厂提供以下内容:一个File对象或一个InputStream,而不是一个Reader

切换到XMLStreamReader

除此之外,您将获得XMLStreamReaderXMLEventReader. 这是因为XMLEvent实例成本高昂,因为即使创建它们的解析器已经转移,它们也能保持可用。这意味着 aXMLEvent是相对重量级的,它在创建时保存了所有可能的相关信息(命名空间上下文、所有属性……),这需要构建成本以及在内存中保存的成本。

解析完成后,事件可以被缓存和引用。

XMLStreamReader不发出任何事件,因此不支付此价格。由于您只需要读取文本值并且在解析后没有用处XMLEvent,因此流读取器将产生更好的性能。

切换到更快的XMLStreamReader

上次我检查时(有点太久以前),Woodstox比 JDK 标准 Stax 实现(源自 Apache Xerces)要快得多。但周围可能有更快的孩子

尝试其他 XML 吗?

我非常怀疑您是否能从任何其他解析技术中获得更快的性能(SAX 通常是等效的,但您实际上不必在找到相关标签后就选择退出解析)。XSLT 相当快,但它所显示的强大功能却是以性能为代价的(通常会构建某种轻量级 DOM 树)。XPath 也是如此,表达式的表现力通常意味着底层保留着某种复杂的结构。当然,DOM 通常要慢得多。

不做 XML 怎么样?

如果已经进行了所有其他优化,并且您知道 XML 处理是瓶颈(不是 IO,不是其他任何东西,只是 中的 XML 处理和本身)。

正如 @MichaelKay 在评论中指出的那样,不使用 XML 工具可能会在将来的任何时候崩溃,因为文件的创建方式虽然在 XML 中完全等效,但可能会发展并破坏基于简单文本的工具。

使用纯粹基于文本的工具,您可能会被命名空间声明的更改、不同的换行符、HTML 实体编码、外部引用和许多其他 XML 特定的微妙之处所迷惑,以获得一小部分额外的性能。

多线程处理您的进程

使用多线程可能是一种解决方案,但也有一些注意事项。

如果您的进程在典型的 EE 服务器实现中运行,具有高级配置和任何适当的负载,则多线程并不总是获胜,因为系统可能已经缺乏资源来生成额外的线程,并且/或者您可能会击败内部优化通过在其托管设施之外创建线程来控制服务器。

如果您的进程是所谓的轻量级应用程序,或者其典型用法只需要少数用户同时使用它,那么您不太可能遇到此类问题,并且您可能会考虑生成一个ExecutorService并行执行 XML 解析的进程。

另一件需要考虑的事情是 IO。单个文件的 XML 处理(就 CPU 而言)应尽可能从解析的并行化中获益。但您可能会受到流程其他部分(通常是 IO)的瓶颈。如果您在单个 CPU 中解析 XML 的速度比从磁盘中提取数据的速度快,那么并行化就没用了,您会得到许多线程等待磁盘,这可能会使您的系统饿死不多(如果有的话) 。所以你必须相应地调整。

改变流程

如果您被困在单个工作单元中读取“大文件”或数千个小文件,那么这可能是一个退后一步并查看您的流程的好机会。

  1. 读取数千个小文件会产生 IO 和系统调用方面的成本,这实际上是阻塞调用。你的java进程必须等待来自系统级的数据。如果您有办法最大限度地减少系统调用的数量(打开更少的文件,使用更大的缓冲区......),这可能是一个胜利。我的意思是:读取单个tar 文件(包含 2000 个小 xml - 几个 kbs - 文件)通常比读取 2000 个单独文件更快。

  2. 先发制人/即时完成工作。为什么要等到用户请求数据时才解析 XML?数据一到达系统就无法解析它(也许是异步的?)。这将为您省去从磁盘读取数据的麻烦,并且可能让您有机会插入到无论如何都会解析文件的进程中,从而在两种情况下都节省时间。然后,当用户请求到来时,您只需查询结果(在某种数据库中)?

向前走

如果不衡量东西,就无法提高绩效。

所以:测量。

IO 费用是多少?

XML 处理的成本是多少?以及它的哪一部分?(在您的示例代码中,每个文件的 XMLInputFactory` 的无用初始化意味着如果您刚刚使用分析器对其进行了测量,则可以获得很多好处)

您的服务电话中的其他内容要花多少钱?(您是否在调用之前/之后连接到数据库?在每个文件中?是否可以以不同的方式完成)。

如果您仍然遇到困难,您可以使用这些发现编辑您的问题,以获得进一步的帮助。

  • 谢谢。我在很大程度上同意你的评论。我提到(本意是提及,而不是建议,但我会编辑)这只是因为OP提出的问题是读取单个大文件,该文件不是XML而是单个XML的“猫”,所以原始格式已经暗示了对文本级输入的混乱。我会让这一点更清楚。 (2认同)