高效使用内存的XSLT,用于转换大型XML文件

4
这个问题与 michael.hor257k 最近的回答 有关,该回答又与 Dimitre Novatchev 的回答 有关。
当使用上述回答(由 michael.hor257k 提供)的样式表对一个大型 XML 进行转换(大约 60MB,下面提供了示例 XML),转换成功了。
当尝试另一个样式表时,它与 michael.hor257k 的略有不同,旨在递归地对元素进行分组(具有子元素 sectPr)和它们的后继兄弟元素(直到下一个具有子元素 sectPr 的后继兄弟元素),即将元素分组到输入 XML 的深度。
示例输入 XML:
<body>
    <p/>
    <p>
        <sectPr/>
    </p>
    <p/>
    <p/>
    <tbl/>
    <p>
        <sectPr/>
    </p>
    <p/>
</body>

我尝试的样式表:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="*">
        <xsl:copy>
            <xsl:apply-templates select="*[1] | *[sectPr]"/>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::*[1][not(sectPr)]"/>
    </xsl:template>

    <xsl:template match="*[sectPr]">
        <myTag>
            <xsl:copy>
                <xsl:apply-templates select="*[1] | *[sectPr]"/>
            </xsl:copy>
            <xsl:apply-templates select="following-sibling::*[1][not(sectPr)]"/>
        </myTag>
    </xsl:template>

</xsl:stylesheet>

好奇心驱使我在处理大约60MB大小的XML时遇到了OutOfMemoryError错误。

我想知道,我似乎不明白michael.hor257k和Dimitre Novatchev提供的XSLT背后的诀窍,它们为什么不会导致内存异常。

我的样式表与上述答案之间的主要区别是什么导致我出现了OutOfMemoryError。如何更新样式表以提高内存效率。


@FoggyDay 它是 Windows 系统,使用的是 saxon-6.5.5.jar 和 saxon9.jar。这是一台拥有 3GB RAM 的机器。但是问题更加具体,与样式表的编写方式有关,正如 michael.hor257k 提到的答案可以无缝地进行转换。 - Lingamurthy CS
重点是,“60MB”是一个相对较小的大小,即使您“优化”样式表,也容易出现“内存不足”错误。问题:当您尝试增加JRE内存时会发生什么,例如java -Xmx2048m -jar MyProg.jar myXML.xml?问题:您是否尝试查看JVisualVM中的不同使用模式?问题:您是否控制Java应用程序,以便可以尝试优化,例如clearDocumentPool() - FoggyDay
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Dimitre Novatchev
1
Lingamurthy CS,请添加<xsl:strip-space elements="*"/>声明,该声明已从原始解决方案中删除。这将从源XML文档中剥离任何仅包含空格的文本节点。不剥离这些节点可能会显著增加节点数和内存占用量--在您的情况下,与剥离这些节点相比,持有XML文档所需的内存将几乎增加一倍。我已成功运行了您的转换,但是在剥离节点后,它的运行速度提高了20%。您的计算机有多少GB的RAM? - Dimitre Novatchev
1
Lingamurthy CS,我运行了你的转换 - 一次是按照问题中发布的方式,第二次添加了<xsl:strip-space elements="*"/>,使用Saxon 9.1J - 因为它还显示了转换的内存消耗。两次运行都成功了。在第一种情况下,处理的节点数为925004,使用了340MB RAM。转换花费了5.3秒。在第二种情况下,节点数为4336366,使用了215 MB RAM。转换运行时间为5.06秒。 - Dimitre Novatchev
显示剩余5条评论
2个回答

6

Lingamurthy CS,

请将您从原始方案中删除的<xsl:strip-space elements="*"/>声明添加回来。这将从源XML文档中剥离任何仅包含空格的文本节点。

不剥离这些节点可能会显著增加节点数和内存占用量,在您的情况下,与剥离这些节点的XML文档相比,需要保存XML文档所需的内存将几乎增加一倍。

我已经运行了您的转换,但在剥离这些节点后,它的运行速度提高了20%-在MS XslCompiledTransform上。

然后我运行了您的转换--第一次按照问题发布的方式运行,第二次使用Saxon 9.1J添加了<xsl:strip-space elements="*"/>--因为它还显示了转换的内存消耗。两次运行都成功了。在第一种情况下,处理的节点数为9525004,使用了340MB RAM。该转换花费了5.3秒钟。在第二种情况下,处理的节点数量为4336366,使用了215MB RAM。转换在5.06秒内运行。


那个数字925004是否有一个数字被删除了? - Michael Kay
@MichaelKay,是的,但我不记得是哪个了... :) 它是9M+。 - Dimitre Novatchev
@MichaelKay,已更正。该数字为:9525004 - Dimitre Novatchev

2
根据我的经验,XSLT很容易使内存效率低下。它非常适合较小的转换(甚至是许多文件的小转换),但当您开始进行复杂的分组或轴遍历时,对于大型(15mb+)XML文件而言,它变得效率低下。将您的大文件拆分成小文件是否可行?我以前使用过这种技术来解决此类问题。
由于您正在使用Windows,因此还有其他几个选项(特别是您仅使用XSLT 1.0)。其中一个可能有效的方法是尝试使用.NET的XslCompiledTransform类,它将您的XSLT编译为IL代码。这可能无法解决内存问题,但在您的平台上可能会更有效。
另一个选择是利用.NET的XmlReader和XmlWriter类,考虑到您的要求,实现起来可能不是很困难。这些是单向XML读写类。利用流式处理可以实现更高的内存效率。

1
感谢您的回答,阅读起来很好。这里唯一的目的是找出我XSLT与我所参考的答案之间的主要区别,正如DimitreNovatchev正确指出的那样,这使得我的XSLT内存效率较低。我会记住您的建议,以备将来参考。 - Lingamurthy CS

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