Java XPath.evaluate性能在多次调用中会减慢(荒谬地)

Java XPath.evaluate性能在多次调用中会减慢(荒谬地),java,android,performance,xpath,Java,Android,Performance,Xpath,我试图使用javax.xml.xpath包在具有多个名称空间的文档上运行xpath表达式,但我遇到了愚蠢的性能问题 我的测试文档来自一个真实的生产示例。它大约有60万xml。该文档是一个相当复杂的Atom提要 我意识到,我使用XPath所做的事情可以在没有XPath的情况下完成。然而,同样的实现在其他性能极为低下的平台上表现得好得离谱。现在,重建我的系统以不使用XPath超出了我在现有时间内所能做的范围 我的测试代码如下所示: void testXPathPerformance() {

我试图使用javax.xml.xpath包在具有多个名称空间的文档上运行xpath表达式,但我遇到了愚蠢的性能问题

我的测试文档来自一个真实的生产示例。它大约有60万xml。该文档是一个相当复杂的Atom提要

我意识到,我使用XPath所做的事情可以在没有XPath的情况下完成。然而,同样的实现在其他性能极为低下的平台上表现得好得离谱。现在,重建我的系统以不使用XPath超出了我在现有时间内所能做的范围

我的测试代码如下所示:



void testXPathPerformance()
{
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    DocumentBuilder builder = factory.newDocumentBuilder();

    Document doc = builder.parse(loadTestDocument());

    XPathFactory xpf = XPathFactory.newInstance();
    XPath xp = xpf.newXPath();

    NamespaceContext names = loadTestNamespaces();
    //there are 12 namespaces in names.  In this example code, I'm using
    //'samplens' instead of the actual namespaces that my application uses
    //for simplicity.  In my real code, the queries are different text, but
    //precisely the same complexity.

    xp.setNamespaceContext(names);

    NodeList nodes = (NodeList) xp.evaluate("/atom:feed/atom:entry",
                     doc.getDocumentElement(), XPathConstants.NODESET);


    for(int i=0;i<nodes.getLength();i++)
    {
        printTimestamp(1);
        xp.evaluate("atom:id/text()", nodes.item(i));
        printTimestamp(2);
        xp.evaluate("samplens:fieldA/text()", nodes.item(i));
        printTimestamp(3);
        xp.evaluate("atom:author/atom:uri/text()", nodes.item(i));
        printTimestamp(4);
        xp.evaluate("samplens:fieldA/samplens:fieldB/&at;attrC", nodes.item(i));
        printTimestamp(5);

        //etc.  My real example has 10 of these xp.evaluate lines

     }
}

void testXPathPerformance()
{
DocumentBuilderFactory工厂=DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder=factory.newDocumentBuilder();
Document doc=builder.parse(loadTestDocument());
XPathFactory xpf=XPathFactory.newInstance();
XPath xp=xpf.newXPath();
NamespaceContext names=loadTestNamespaces();
//名称中有12个名称空间
//“samplens”而不是我的应用程序使用的实际名称空间
//为了简单起见,在我的真实代码中,查询是不同的文本,但是
//完全相同的复杂性。
xp.setNamespaceContext(名称);
NodeList节点=(NodeList)xp.evaluate(“/atom:feed/atom:entry”,
doc.getDocumentElement(),XPathConstants.NODESET);
对于(int i=0;i=0;i-)
),则前几个节点的速度为500ms-600ms,最后几个节点的速度为10ms-20ms。因此,这似乎与调用的数量无关,但上下文接近文档末尾的表达式比上下文接近文档开头的表达式的时间要长


有人对我能做些什么有什么想法吗?

这似乎是另一种情况,使用XPath似乎很慢,但不是XPath,原因可能是DOM方法
nodelist.item(I)

Java中的
节点列表的默认实现具有某些特性:

  • 它被惰性地评估
  • DOM列表是实时的
  • 它被实现为一个链表
  • 列表中有一些缓存
  • 当您单独查看这些特性时,您可能会想,为什么XPath表达式的结果对象应该具有这样的特性,但是当您将它们放在一起时,它们更有意义

    1) 延迟评估可能会模糊性能瓶颈的位置。因此,返回节点列表的速度似乎很快,但如果任务总是在列表中迭代,则或多或少会延迟性能成本。如果每次执行网元时都必须重新处理整个列表的评估,则延迟评估的成本会很高读取列表中的xt项

    2)
    NodeList
    成为“活的”列表意味着它将被更新并引用当前在文档树中的节点,而不是最初构建列表时在树中的节点或这些节点的克隆。这是DOM初学者需要掌握的一项重要功能。例如,如果您选择同级元素的
    NodeList
    ,并尝试添加一个新的si将元素添加到每个节点,执行一步到
    项(i+1)
    将始终到达最新添加的节点,循环将永远不会完成

    3) 正在运行的列表也给出了一些解释,说明了为什么它被实现为链表(或者说,实际上的实现是一个双链表)。在测试中可以清楚地看到,访问最后的元素总是最慢的,无论是向后还是向前迭代

    4) 由于缓存,如果缓存保持干净,在单个列表上循环而不导致对树的任何更改应该是相当有效的。在某些Java版本中,这种缓存存在问题。我没有调查所有的过程都会使缓存无效,但最安全的赌注可能是建议保留已评估的缓存xpression相同,不更改树,一次循环一个列表,并始终单步执行下一个或上一个列表项


    真正的性能赢家当然取决于用例。你不应该仅仅调整列表循环,而应该尝试完全取消活动列表的循环-至少作为参考。克隆会使列表不活动。直接访问节点可以通过将节点复制到数组来实现。如果结构合适,你可以n还可以使用其他DOM方法,如
    getNextSibling()
    ,它可以提供比在节点列表上循环更有效的结果。

    尝试在顶部的循环中添加此代码

    Node singleNode = nodes.item(i);
    singleNode.getParentNode().removeChild(singleNode);
    
    然后使用
    singleNode
    变量而不是
    nodes.项(i);
    (当然,您可以更改名称)

    这样做会将正在使用的节点从大型主文档中分离出来。这将大大加快评估方法的处理时间

    例:


    for(int i=0;i这有点晚了,但我遇到了同样的情况,但我的文档似乎太大了,其他答案都没有真正解决问题

    最终,我发现,一旦我使用了它,以前需要15秒来解析的文档只需要几毫秒

    不幸的是,Jaxen的文档记录相当糟糕,但工作非常好:

    DOMXPath myXPath = new DOMXPath("atom:id/text()");
    String myContent = myXPath.stringValueOf(myDocument);
    
    可以在此处找到Java文档

    尝试克隆节点(这样您就不会有来自其祖先的不必要引用)


    如果删除子节点,您将丢失引用,并且只获得要处理的一半节点。

    每次从节点列表中获取节点时,似乎它都会保留对整个xml结构的引用;因此 在导航节点时,xpath
    DOMXPath myXPath = new DOMXPath("atom:id/text()");
    String myContent = myXPath.stringValueOf(myDocument);
    
    Node singleNode = nodes.item(i).cloneNode(true);
    
    private String nodeToString(Node node) {
              StringWriter sw = new StringWriter();
              try {
                Transformer t = TransformerFactory.newInstance().newTransformer();
                t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
                t.transform(new DOMSource(node), new StreamResult(sw));
              } catch (TransformerException te) {
                System.out.println("nodeToString Transformer Exception");
              }
              return sw.toString();
            }
    
    String xml = nodeToString(node);
    
    Element nodeNew =  DocumentBuilderFactory
            .newInstance()
            .newDocumentBuilder()
            .parse(new ByteArrayInputStream(xml.getBytes()))
            .getDocumentElement();
    
    node = nodeNew;