【问题标题】:Most performative way to go through an XML transformation - Java进行 XML 转换的最高效方式 - Java
【发布时间】:2019-11-27 19:46:10
【问题描述】:

我是 java 新手,我想为社区提供意见。 我有一个巨大的 XML,其中包含很多信息。实际上,这个 XML 有大约 140Mb 的信息。 在这个 XML 中,我有很多不再有效的信息,所以我需要过滤并只使用有效的信息,要检查这一点,我需要在节点之间交叉信息,检查是否需要删除。在某些情况下,需要删除整个父(主)节点。

我已经在使用 dom 解析,使用循环,在循环内我保存在变量中并交叉信息以检查,并删除实际节点或整个父节点。

基本上结构是这样的:

<source>
    <main>
        <id>98567</id>
        <block_information>
            <name>Block A</name>
            <start_date>20120210</start_date>
            <end_date>20150210</end_date>
        </block_information>
        <block_information>
            <name>Block A.01</name>
            <start_date>20150210</start_date>
            <end_date>20251005</end_date>
        </block_information>
        <city_information>
            <name>Manchester</name>
            <start_date>20150210</start_date>
            <end_date>20150212</end_date>
        </city_information>
        <city_information>
            <name>New Manchester</name>
            <start_date>20150212</start_date>
            <end_date>20251005</end_date>
        </city_information>
        <phone>
            <type>C</type>
            <number>987466321</number>
            <name></name>
        </phone>
        <phone>
            <type>P</type>
            <number>36547821</number>
            <name></name>
        </phone>
    </main>
    <main>
        <id>19587</id>
        <block_information>
            <name>Che</name>
            <start_date>20090210</start_date>
            <end_date>20100210</end_date>
        </block_information>
        <block_information>
            <name></name>
            <start_date>20100210</start_date>
            <end_date>20351005</end_date>
        </block_information>
        <city_information>
            <name></name>
            <start_date>20150210</start_date>
            <end_date>20150212</end_date>
        </city_information>
        <city_information>
            <name>No Name</name>
            <start_date>20150212</start_date>
            <end_date>20191005</end_date>
        </city_information>
        <phone>
            <type>C</type>
            <number>987466321</number>
            <name>Mom</name>
        </phone>
        <phone>
            <type>P</type>
            <number>36547821</number>
            <name></name>
        </phone>
    </main>
</source>

输出是这样的:

<result>
        <main>
                <id>98567</id>
                <block_name>Block A.01</block_name>
                <city_name>New Manchester</city_name>
                <cellphone></cellphone>
                <phone>36547821</phone>
                <contact_phone></contact_phone>
                <contact_phone_name></contact_phone_name>
        </main>
</result>

结果出来的信息,&lt;block_information&gt;&lt;city_information&gt;必须有一个有效(&lt;start_date&gt;小于实际日期,&lt;end_date&gt;大于实际日期),&lt;name...&gt;是必须的对彼此而言。 如果没有一个或多个有效,&lt;main&gt; 将被删除。

对于电话号码,&lt;type&gt; ['C' 是联系方式,'P' 是个人电话,'M' 是手机]。因此,如果&lt;type&gt; 是'C',但&lt;name&gt; 中没有值,则电话不会转到结果。 “P”转到&lt;phone&gt;,“M”转到&lt;cellphone&gt;

我希望您考虑什么是最好的方式以最具性能的方式做到这一点,并且任何人都可以在需要时以简单的方式进行调整。

提前感谢您的意见!

按照@kjhughes 的要求,我在示例 XML 中添加了一些值,以及一些我需要做的过滤器。谢谢!

ps.:示例中使用的XML结构与实际相比过于简单,类型要复杂得多。

【问题讨论】:

  • 一旦您指定了过滤哪些节点以及允许通过哪些节点的标准,这就是 XSLT 中的简单身份转换适应。如果您需要更具体的信息,则必须缩小广泛问题的范围。

标签: java dom xpath xml-parsing


【解决方案1】:

XSLT 是一种自 1999 年以来就存在的转换语言,现在有 1.0、2.0 和 3.0 三个版本,最新版本是 2017 年作为 W3C 推荐发布的,并由 Saxon 9.8 及更高版本在 Java 平台上提供支持,可在 open- Sourceforge 和 Maven 上的源 HE 版本。通过合并 Apache Xalan,Oracle/Sun Java JRE 支持使用 XSLT 1。

因此,您可以选择使用 XSLT,而不是使用 DOM,这里是使用 XSLT 3 的示例(在线 https://xsltfiddle.liberty-development.net/bFN1yab/0):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:output indent="yes"/>

  <xsl:function name="mf:date" as="xs:date">
      <xsl:param name="input-date" as="xs:string"/>
      <xsl:sequence
         select="xs:date(replace($input-date, '([0-9]{4})([0-9]{2})([0-9]{2})', '$1-$2-$3'))"/>
  </xsl:function>

  <xsl:function name="mf:select-valid-info" as="element()*">
      <xsl:param name="infos" as="element()*"/>
      <xsl:sequence
         select="$infos[name/normalize-space()
                       and mf:date(start_date) lt current-date()
                       and mf:date(end_date) gt current-date()]"/>
  </xsl:function>

  <xsl:function name="mf:valid-main" as="xs:boolean">
      <xsl:param name="main" as="element(main)"/>
      <xsl:sequence
        select="let $valid-blocks := mf:select-valid-info($main/block_information),
                    $valid-cities := mf:select-valid-info($main/city_information)
                return count($valid-blocks) eq 1 and count($valid-cities) eq 1"/>
  </xsl:function>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:template match="main[not(mf:valid-main(.))]"/>

  <xsl:template match="main[mf:valid-main(.)]">
      <xsl:copy>
          <xsl:apply-templates 
            select="id,
                    mf:select-valid-info(block_information)/name,
                    mf:select-valid-info(city_information)/name,
                    phone"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="block_information/name | city_information/name">
      <xsl:element name="{substring-before(local-name(..), '_')}_name">
          <xsl:value-of select="."/>
      </xsl:element>
  </xsl:template>

  <xsl:template match="main/phone[type = 'C']">
      <contact_phone>
          <xsl:value-of select="number[current()/normalize-space(name)]"/>
      </contact_phone>
      <contact_name>
          <xsl:value-of select="name"/>
      </contact_name>
  </xsl:template>

  <xsl:template match="main/phone[type = 'P']">
      <phone>
          <xsl:value-of select="number"/>
      </phone>
  </xsl:template>

  <xsl:template match="main/phone[type = 'M']">
      <cellphone>
          <xsl:value-of select="number"/>
      </cellphone>
  </xsl:template>

</xsl:stylesheet>

我希望我已经掌握了main元素的条件,我还没有完全理解各种电话数据的规则,但代码无论如何都是作为示例的。

当然性能在很大程度上取决于实现,但我认为 XSLT 是一种比 DOM 编码更结构化和可维护的方式。

如果您负担得起,您还可以查看支持流式 XSLT 3 的 Saxon 9.8 或 9.9 EE,通过对上述代码进行一些重写,您可以使用基于 XSLT 的方法来仅通过巨大的文档进行流式转发,具体化 @ 987654324@ 元素作为您转换的元素节点,同时保持较低的内存占用,因为与 DOM 或普通 XSLT 处理相比,这种方法不会首先将整个 XML 文档解析为完整的内存树结构:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"

    version="3.0">

    <xsl:mode streamable="yes" on-no-match="shallow-copy"/>

    <xsl:template match="source">
        <xsl:copy>
            <xsl:apply-templates select="main!copy-of()" mode="main"/>
        </xsl:copy>
    </xsl:template>

    <xsl:output indent="yes"/>

    <xsl:function name="mf:date" as="xs:date">
        <xsl:param name="input-date" as="xs:string"/>
        <xsl:sequence
            select="xs:date(replace($input-date, '([0-9]{4})([0-9]{2})([0-9]{2})', '$1-$2-$3'))"/>
    </xsl:function>

    <xsl:function name="mf:select-valid-info" as="element()*">
        <xsl:param name="infos" as="element()*"/>
        <xsl:sequence
            select="$infos[name/normalize-space()
            and mf:date(start_date) lt current-date()
            and mf:date(end_date) gt current-date()]"/>
    </xsl:function>

    <xsl:function name="mf:valid-main" as="xs:boolean">
        <xsl:param name="main" as="element(main)"/>
        <xsl:sequence
            select="let $valid-blocks := mf:select-valid-info($main/block_information),
            $valid-cities := mf:select-valid-info($main/city_information)
            return count($valid-blocks) eq 1 and count($valid-cities) eq 1"/>
    </xsl:function>

    <xsl:mode name="main" on-no-match="shallow-copy"/>

    <xsl:template match="main[not(mf:valid-main(.))]" mode="main"/>

    <xsl:template match="main[mf:valid-main(.)]" mode="main">
        <xsl:copy>
            <xsl:apply-templates 
                select="id,
                mf:select-valid-info(block_information)/name,
                mf:select-valid-info(city_information)/name,
                phone" mode="#current"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="block_information/name | city_information/name" mode="main">
        <xsl:element name="{substring-before(local-name(..), '_')}_name">
            <xsl:value-of select="."/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="main/phone[type = 'C']" mode="main">
        <contact_phone>
            <xsl:value-of select="number[current()/normalize-space(name)]"/>
        </contact_phone>
        <contact_name>
            <xsl:value-of select="name"/>
        </contact_name>
    </xsl:template>

    <xsl:template match="main/phone[type = 'P']" mode="main">
        <phone>
            <xsl:value-of select="number"/>
        </phone>
    </xsl:template>

    <xsl:template match="main/phone[type = 'M']" mode="main">
        <cellphone>
            <xsl:value-of select="number"/>
        </cellphone>
    </xsl:template>

</xsl:stylesheet>

【讨论】:

    【解决方案2】:

    我会采用以下方法:

    • 找到一个库,可以让您流式传输 xml(文件或输入流)并生成 Stream&lt;Main&gt;
    • 处理 Stream&lt;Main&gt; 并根据您的验证逻辑过滤每个 Main 节点
    • 取决于您是 I/O 还是 CPU 瓶颈,使用.parallel() 流来处理流(阅读:测试.parallel() 是否对您有任何帮助)

    这足以满足 XML 解析上下文中的任何合理的性能要求(我猜?)。 Google for Java XML Stream 并从那里开始(或者this stackoverflow 问题可以提供一些指示)

    【讨论】:

    • 谢谢!我会搜索并看看这个,当我学到一些东西时我会回来!我使用 inputstream,然后使用 dom.parse,然后节点到节点进行检查和删除。主要目标是看看这种方式是最好的还是有另一种方式:)
    • 祝你好运!关键是不要将dom.parse 整个文件作为一个整体,这就是一些库派上用场的地方,可以让您流式传输节点:)
    猜你喜欢
    • 2016-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多