Java解析大型XML文档

3

我正在尝试解析和替换一个大约45MB大小的XML文件中的值。我的做法是:

private void replaceData(File xmlFile, File out)
{
    DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = df.newDocumentBuilder();
    Document xmlDoc = db.parse(xmlFile);
    xmlDoc.getDocumentElement().normalize();

    Node allData = xmlDoc.getElementsByTagName("Data").item(0);
    Element ctrlData = getSubElement(allData, "ctrlData");
    NodeList subData = ctrlData.getElementsByTagName("SubData");

    int len = subData.getLength();

    for (int logIndex = 0; logIndex < len; logIndex++) {

        Node log = subData.item(logIndex);
        Element info = getSubElement(log, "info");
        Element value = getSubElement(info, "dailyInfo");
        Node valueNode = value.getElementsByTagName("value").item(0);
        valueNode.setTextContent("blah");               
    }

    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer t = tf.newTransformer();
    DOMSource s = new DOMSource(xmlDoc);
    StreamResult r = new StreamResult(out);
    t.transform(s, r);

    } catch (TransformerException | ParserConfigurationException | SAXException | IOException e) {
         throw e;
    }
}

private static Element getSubElement(Node node, String elementName)
{
        return (Element)((Element)node).getElementsByTagName(elementName).item(0);
}

我注意到随着for循环的进行,所需时间越长。对于平均100k节点的情况,需要超过2小时的时间,而如果手动分解为1k的较小块,则只需要约10秒钟。这个文档被解析的方式是否存在效率问题?
----编辑----
根据评论和答案,我转而使用Sax和XmlStreamWriter。参考/示例在此处:http://www.mkyong.com/java/how-to-read-xml-file-in-java-sax-parser/ 转向使用SAX后,replaceData函数的内存使用不会扩展到XML文件的大小,并且XML文件处理时间平均为18秒。

dimensionValue 是从哪里来的?它去了哪里?dimension 也是同样的情况。 - Bob Dalgleish
啊,抱歉,当我简化代码时忘记编辑了。那些值是数组(int [])查找。 - Niru
如果我遇到类似的问题,我会使用一个简单的方法:让代码在调试器中运行,并经常随机停止。它最常停止的地方就是最慢的地方。非常简单,相当有效。祝你玩得愉快。 - Bernd Ebertz
2
使用SAX API而不是将整个DOM加载到内存中会更加高效。诚然,代码可能不太易读... - Ralf
我没有看到使用SAX解析器替换XML值的方法。那么,难道不是只需使用字符串缓冲区逐行读取XML文件并替换值吗? - Niru
您可以通过SAX API读写XML。以下代码将完整的文档加载到内存中:Document xmlDoc = db.parse(xmlFile); - Ralf
2个回答

3
正如评论中提到的,将整个DOM加载到内存中,特别是对于大型XML来说,效率非常低下,因此更好的方法是使用消耗恒定内存的SAX解析器。缺点在于,您无法获得在内存中拥有整个DOM的流畅API,并且如果要在嵌套节点中执行复杂的回调逻辑,则可见性非常有限。
如果您只想解析特定节点和节点族,而不是解析整个XML,那么有一个更好的解决方案,可以让您兼顾两全,并已被记录在博客文章(链接1)和开源项目(链接2)中。它基本上是在SAX解析器之上的一个非常轻量级的包装器,您可以在其中注册您感兴趣的XML元素,并在获取回调时使用它们对应的部分DOM到XPath。
这样你就可以将复杂度保持在常数时间(如上述博客所记录的,扩展到超过1GB的XML文件),同时保持XPath对你感兴趣的XML元素的DOM的流畅操作。

谢谢,我已经转而使用SAX和XmlStreamWriter,并使用这里的示例:http://www.mkyong.com/java/how-to-read-xml-file-in-java-sax-parser/。对于相同的数据集,读取/替换/写入现在只需要18秒。 - Niru

2
为什么你使用Java来完成这个任务,XSLT是为此而设计的呢?
45Mb的文件虽然很大,但仍然可行。像Saxon这样的好的XSLT处理器所使用的树模型比通用的DOM更有效率(在存储空间和搜索速度方面),因为它们是只读的。而且XSLT有更多的优化代码的空间。
我无法从你的代码中逆向工程出你的规范,但我没有看到任何本质上非线性的东西。我不认为在Saxon中完成这个任务需要超过10分钟。

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