如何在Java中转换大型XML文件?

9

正如标题所述,我有一个巨大的XML文件(几GB大小)

<root>  
<keep>  
   <stuff>  ...  </stuff>  
   <morestuff> ... </morestuff>  
</keep>  
<discard>  
   <stuff>  ...  </stuff>  
   <morestuff> ... </morestuff>
</discard>  
</root>  

我希望将它转换为一个更小的文件,只保留其中几个元素。我的解析器应该执行以下操作:
1. 解析文件,直到出现相关元素。
2. 将整个相关元素(包括子元素)复制到输出文件。返回1。
使用SAX解析器很容易完成第1步,但对于DOM解析器来说却无法实现。
使用SAX解析器进行第2步很麻烦,但使用DOM解析器或XSLT则很容易。
那么有没有巧妙的方法可以结合SAX和DOM解析器来完成这个任务呢?
7个回答

10

StAX 似乎是一个显而易见的解决方案:它是一种拉模式的解析器,而不是 SAX 的“推模式”或 DOM 的“缓存整个文档”的方式。虽然我没有使用过它。可以通过“StAX 教程”搜索来了解更多信息 :)


1
+1 如果之前没有处理过XML文件,那么StAX比SAX更容易使用。此外,它还允许编写XML(与SAX相反)。 - helpermethod

9

是的,只需编写一个SAX内容处理程序,当它遇到某个元素时,在该元素上构建一个dom树。我已经用这种方法处理过非常大的文件,效果非常好。

实际上很容易:一旦您遇到想要的元素的开头,就在您的内容处理程序中设置一个标志,然后从那里开始,将所有内容转发给DOM构建器。当您遇到元素的结尾时,将标志设置为false,并输出结果。

(对于嵌套相同元素名称的更复杂情况,您需要创建一个堆栈或计数器,但这仍然很容易做到。)


5
我曾使用STXXML流转换)获得良好的经验。基本上,它是XSLT的流版本,非常适合解析大量数据且内存占用极小。它有一个名为Joost的Java实现。
应该很容易编写一个STX转换器,忽略所有元素,直到元素与给定的XPath匹配,复制该元素及其所有子元素(在模板组中使用身份模板),并继续忽略元素,直到下一个匹配。 更新 我拼凑出了一个STX转换器,它可以做到我理解你想要的功能。它主要依赖于STX独有的功能,如模板组和可配置的默认模板。
<stx:transform xmlns:stx="http://stx.sourceforge.net/2002/ns"
    version="1.0" pass-through="none" output-method="xml">
    <stx:template match="element/child">
        <stx:process-self group="copy" />
    </stx:template>
    <stx:group name="copy" pass-through="all">
    </stx:group>
</stx:transform>
pass-through="none"stx:transform中配置默认模板(用于节点、属性等)不产生输出,但处理子元素。然后stx:template匹配XPath element/child(这是您放置匹配表达式的位置),它在“copy”组中“处理自身”,这意味着当前元素上调用了来自group name="copy"的匹配模板。该组具有pass-though="all",因此默认模板复制其输入并处理子元素。当结束element/child元素时,控制权被传递回调用process-self的模板,随后的元素再次被忽略,直到模板再次匹配。
以下是一个示例输入文件:
<root>
    <child attribute="no-parent, so no copy">
    </child>
    <element id="id1">
        <child attribute="value1">
            text1<b>bold</b>
        </child>
    </element>
    <element id="id2">
        <child attribute="value2">
            text2
            <x:childX xmlns:x="http://x.example.com/x">
            <!-- comment -->
                yet more<b i="i" x:i="x-i" ></b>
            </x:childX>
        </child>
    </element>
</root>

这是相应的输出文件:

<?xml version="1.0" encoding="UTF-8"?>
<child attribute="value1">
            text1<b>bold</b>
        </child><child attribute="value2">
            text2
            <x:childX xmlns:x="http://x.example.com/x">
            <!-- comment -->
                yet more<b i="i" x:i="x-i" />
            </x:childX>
        </child>

这种不寻常的格式是由于跳过包含换行符的文本节点而产生的,这些节点在child元素之外。

听起来不错。我可以只编写一个XSLT样式表,然后在STX上运行它吗? - user306708
不,这是不可能的。虽然XSLT使用模式来区分不同情况下相同匹配的模板(跳过模式与复制模式,在您的情况下),但STX使用模板组。模板内语法类似于XSLT,但细节不同。我在我的答案中添加了一个示例转换。 - Christian Semrau
请注意,在匹配模板的XPath中,您只能访问当前节点、其父节点和它们的属性。由于转换的流式特性,您无法匹配任何其他先前或后续的节点。如果您需要这种匹配,可以定义可变的变量,并在stx:if测试中使用它们。但这很棘手,感觉就像在XML中实现内容处理程序一样。 - Christian Semrau

3

既然您谈到GB,我更愿意优先考虑内存使用情况。 SAX需要的内存约为文档大小的2倍,而DOM则需要至少是文档大小的5倍。因此,如果您的XML文件大小为1GB,则DOM需要至少5GB的空闲内存。这不再有趣了。所以SAX(或其任何变体,如StAX)是最好的选择。

如果您想要最节省内存的方法,请查看VTD-XML。它仅需要比文件大小稍微多一点的内存。


好的,记忆力在这里绝对至关重要。顺便说一下,SAX甚至不需要文档的两倍大小——因为它是一个流API,你可以随时垃圾回收文档的前面部分,只要你不再需要它们。 - Chris Lercher
真的,但这取决于功能要求。例如,他可能需要在能够收集所需信息之前将整个XML存储在内存中。 - BalusC

2

2
对于这样一个庞大的XML文档,像Omnimark这样具有流式架构的工具是理想的选择。
而且,你不需要编写任何复杂的代码。下面这段Omnimark脚本就可以满足你的需求:
process

submit #main-input

macro upto (arg string) is
    ((lookahead not string) any)*
macro-end

find (("<keep") upto ("</keep>") "</keep>")=>keep
    output keep

find any

0

你可以很容易地使用javax.xml.stream包中的XMLEventReader和多个XMLEventWriter来完成这个操作。


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