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

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

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());
                }

            }
        }
    }
}

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...

3个回答

8

未来有几种可能的方法。

分割文件

首先,如果您的大文件实际上是几个连接在一起的XML文件(如您展示的示例),那么这个大文件并不是一个(有效的)XML文件,我建议在将其传递给严格的XML解析库(Stax、DOM、SAX、XSL等)之前对其进行拆分。

有效的XML文件只有一个文本引导和一个根元素。

您可以使用XML文本引导作为分割标记,使用纯IO /字节级别API(不涉及XML)。

然后,可以将每个分割视为单个XML“文件”(必要时可独立处理,以用于多线程目的)。 我指的不是“文件”,它可能是从原始“大文件”中分割出的byte[]块。

加速XML解析

关于您的代码

在您的示例代码中,使用XMLEventReader时有一些需要注意的地方。

  1. 您不应像现在这样迭代属性。除非我漏掉了什么,否则您没有执行任何操作。
  2. 一旦到达名称为displayNameSTART_ELEMENT,则应调用getElementText,该方法作为解析器的内部方法具有一些用于提高速度的优化技巧。这个调用将使阅读器停留在匹配的END_ELEMENT处,因此您的代码会简化得多(仅检查displayName START_ELEMENT,就可以了)。
  3. 由于您的XML已经格式良好,因此可以在找到结果后跳过解析。
  4. XMLInputFactories应当被重复使用,因此不要为每个文件创建一个实例,而是创建一个共享实例。
  5. XML(xxx)Reader是可关闭的,因此请关闭它们。
  6. 一些XML库具有比JDK提供的字符编码方案更快的速度(了解XML编码的内部原理使它们能够做到这一点),因此如果您在文件开头有一个有效的XML文本引导声明编码,则应使用File对象或InputStream来提供给工厂,而不是Reader

切换到XMLStreamReader

除此之外,XMLStreamReaderXMLEventReader 具有更快的性能。这是因为XMLEvent 实例很昂贵,因为它们具有即使创建它们的解析器已经继续工作的能力。这意味着XMLEvent 相对较重,它在创建时保存了每个可能相关的信息(命名空间上下文,所有属性等),这将带来构建成本和内存保留成本。

解析完成后可以缓存并引用事件。

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。CPU方面,对个别文件进行XML处理应尽可能从解析的并行化中获益。但是您可能会被过程的其他部分(通常是IO)阻塞。如果您可以在单个CPU上快速解析XML,而无法从磁盘中提取数据,则并行化是没有用的,您将获得许多线程等待磁盘,这可能会导致系统饥饿的资源减少(如果有任何资源的话)。因此,您必须进行适当的调整。

更改进程

如果您在读取“巨大文件”或数千个小文件时卡住了一个工作单元,那么这可能是一个好的机会来回顾您的进程。

  1. 读取数千个小文件需要进行IO和系统调用,这实际上是阻塞调用。您的Java进程必须等待数据从系统级别进出。如果您有一种方式可以最小化系统调用的数量(打开较少的文件,使用更大的缓冲区等...),那么这可能是优势。我的意思是:通常可以比阅读2000个单独的文件更快地阅读单个tar文件(其中包含2000个小XML文件-几KB文件)。

  2. 预先在运行时执行工作。为什么要等到用户请求数据时才解析XML?是否有可能在数据到达系统时立即解析它(可能是异步方式)?那将使您免于从磁盘读取数据的麻烦,并可能给您插入将在任何情况下都解析文件的进程的机会,从而节省两次时间。然后,只需在用户请求时查询结果(在某些数据库中)?

前进

无法通过测量来构建性能。

所以:测量。

IO成本有多高?

XML处理成本有多高?其哪一部分?(在您的示例代码中,对每个文件进行XMLInputFactory的无用初始化意味着如果使用分析器测量,就可以获得很多收益)

您的服务调用的其他部分成本有多高?(您是否在每次文件之前/之后连接到数据库?是否可以以不同的方式完成这项工作)。

如果您还卡在某个问题上,可以将您的发现编辑到问题中,以获取进一步的帮助。


对于“不要使用XML”的建议给一个负分。除非你非常绝望,否则你不想为了稍微提升一点性能而牺牲应用程序的健壮性、可维护性和未来扩展性。我们收到了太多由于这样的取巧导致工作流程出现问题的Stack Overflow问题。 - undefined
2
谢谢。我在很大程度上同意你的评论。我之所以提到这一点(而不是建议),只是因为问题由OP以读取一个单独的巨大文件的方式呈现,该文件不是XML,而是一系列个别的XML文件的“cat”。因此,在原始格式中已经暗示了对文本级输入进行操作。我会让这一点更加清楚明确。 - undefined

0

从我看到的情况来看,你可以使用多线程同时解析3个XML文件,并将对象存储在一个线程安全的列表(如CopyOnWriteArrayList)或线程安全的映射(如ConcurrentHashMap)中。如果你正在使用Stax解析器进行解析,它已经进行了优化,适用于较大的XML文件。此外,如果你不需要从XML中获取所有数据,你可以使用XPath,再次强调XPath和流式XML解析是不同的。


你是什么意思? - undefined
这是我添加的代码:XPathExpression expr = xpath.compile("//WebServiceImpl/displayName/text()"); - undefined
如果你想的话,可以使用XPath API和XML命名空间。 - undefined
1
@kane_004 在这个网站上搜索"XPath默认命名空间",你会找到数百个答案告诉你如何在有命名空间的XML中使用XPath。 - undefined
@Deb 所以我发现我的代码中有这个语句 factory.setNamespaceAware(true); 但不确定它是否起作用? - undefined
显示剩余4条评论

0

数字在哪里?没有测量,你无法解决性能问题。你所达到的性能如何?是一直很差,还是已经接近你可以合理期望的最佳水平了?

我只看到你代码中一个性能上的“失误”,那就是为每个文件创建一个新的解析器工厂(创建工厂非常昂贵,需要检查类路径上的每个JAR包)。但是你让我感到困惑:你说你正在解析一个巨大的文件(实际上,“巨大”是什么意思?),但是你展示的内容似乎是许多小的XML文档的串联。从性能角度来看,这两种用例是完全不同的:对于许多小文档,初始化解析器通常是总体成本的很大一部分。


是的,XML将通过使用API的外部进程提供。解析器将需要读取大约2,400个XML文件,并从每个文件中解析出displayName。使用DOM解析器,我能够在大约1分30秒内完成。使用上述过程中的StAX,我能够在19秒内完成。我正在努力使其尽可能接近0,因为我需要发布结果。 - undefined
每秒处理120个文件意味着你已经在IO和系统调用中花费了相当一段时间。如果你排除这个因素(例如,测量进程的IO等待时间),剩下什么?如果你排除解析器初始化,还剩下什么?(或者反过来说:测量你能够多快只读取这些文件的字符而不进行任何处理,因为那才是你真正无敌的“零秒”。另一方面,通过改变你的进程,难道你没有机会从存档(tar)中读取文件吗?这将消除数千次系统调用,因为你只需要读取一个文件。 - undefined
@GPI 所以我发现我的代码中有这个语句 factory.setNamespaceAware(true); 但不确定它是否起作用? - undefined
我对DOM和StAX之间的差异感到有些惊讶,但你还没有回答我的关于文件大小的问题--“巨大”到底有多大?它是否足够大以致于导致内存抖动?实际上,你需要基于业务需求设定一个目标。“尽可能接近零”就像试图构建一座能够尽可能高达天空的摩天大楼一样;这并不是一个你可以设计实现的实用工程目标。 - undefined
@MichaelKay 所以我正在从API中读取XML,并且正在解析xml中的数据displayName标签,然后将该值作为字符串放入数组中。根据数组中有多少个这样的项,运行所需的时间将会受到影响。我希望尽可能地减少等待时间的原因是,我正在提示前端网站上的屏幕来查看该数组中的结果。我试图让等待时间尽量短。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接