【问题标题】:Java: How to speed up the xpath string generation on a given w3c dom document?Java:如何加快给定 w3c dom 文档的 xpath 字符串生成速度?
【发布时间】:2011-09-23 23:36:42
【问题描述】:

我有以下方法,它采用 org.w3c.dom.Document 并生成绝对 xpath 字符串。

我注意到浏览页面上的数百个元素需要很长时间。

有没有什么办法可以加快速度,或者有什么不同的方法?

重要提示:我只得到 org.w3c.dom 文件

   public String getElementXpath(DOMElement elt){
            String path = "";          

            for (Node fib = (Node) elt; fib != null; fib = fib.getParentNode()){                
                if (fib.getNodeType() == Node.ELEMENT_NODE){

                    DOMElement thisparent = (DOMElement) fib;
                    int idx = getElementIdx(thisparent);
                    String xname = thisparent.getTagName();

                        if (idx >= 1) xname += "[" + idx + "]";
                        path = "/" + xname + path;
                }
            }
            return path;           
        }

        private int getElementIdx(DOMElement elt) {
             int count = 1;
             for (Node sib = elt.getPreviousSibling(); sib != null; sib = sib.getPreviousSibling())
                {
                    if (sib.getNodeType() == Node.ELEMENT_NODE){
                        DOMElement thiselement = (DOMElement) sib;
                        if(thiselement.getTagName().equals(elt.getTagName())){
                            count++;
                        }
                    }
                }

            return count;
        }

【问题讨论】:

    标签: java dom xpath domdocument


    【解决方案1】:

    您的代码在同级数量上为 O(n^2)(即树的最大扇出)。

    考虑到任何 DOM 问题,更好的方法始终是避免使用 DOM!但我不知道这是否适合您的情况。

    一个不太激进的改变是改变你的代码,这样当它遍历一个节点的子节点时,它维护一个哈希图,其中包含遇到的每个元素名称、具有该名称的元素的数量,然后使用此信息来生成下标(索引),而不是通过所有先前的兄弟姐妹倒数。

    【讨论】:

    • 我在读最后一段时迷路了。您的意思是获取该级别中所有元素(例如,A)的总数吗?我如何获得中间某物的索引,而不必从头开始计数或从这个 totalcountIndex 倒数?
    • 还有 O(n^2) 是什么意思....距离我上一堂 comp sci 课已经有一段时间了
    • O(n^2) 表示如果兄弟的数量增加一倍,则执行时间增加四倍;如果兄弟姐妹的数量增加 10 倍,则执行时间增加 100 倍。
    【解决方案2】:

    我不确定您是为每个 DOM 文档中的多个节点还是只为单个节点生成 XPath,但如果您生成多个,那么您可以按照其他人的建议缓存表达式。很难估计,但是如果您想从同一个文档中生成很多 XPath,您不妨反转算法以从根元素开始。请注意,如果您有很多文本节点,您可以规范化文本节点,但我不确定整体性能;)

    但无论如何,对 DOM 节点的迭代确实很快。 但你的字符串处理不是,实际上它有些糟糕。切换到单个 StringBuilder(感谢 Alvin)而不是您当前的方法(使用 + 附加字符串被编译成更复杂的东西,请参阅 javadoc)。确保在构造函数中将其初始化为合适的大小。

    您也不需要检查标签名称,XPath 中允许使用任何名称的元素类型。比如/*[1]/*[2]

    【讨论】:

    • 成功了哇!它快了很多,但仍然不是即时的……是的,我需要为单个节点和多个节点生成很多 xpath。我读了关于 String vs StringBuffer 的东西,我很震惊!
    • 这其实是一个非常好的点。但请注意,如果您的代码在单线程环境中运行,请使用 StringBuilder 而不是 StringBuffer 以避免同步开销。
    • 当你从根元素开始时......如果不测试所有节点,你将如何向下遍历?
    • 啊,是的,不需要同步,更新了响应以反映 Alvin 的评论。
    【解决方案3】:

    === 新 - 所以你需要使用 DOM ===

    为了加快速度,您可以进行缓存(就像其他人建议的那样)。请注意,您当前的代码会多次计算同一节点的 xpath(或者每个节点 N,您必须为 N 的每个子节点计算 N 的 xpath)。这是我对缓存的想法:

    HashMap<Node, String> xpathCache;
    HashMap<Node, Integer> nodeIndexCache;
    
    public String getElementXpath(DOMElement elt){
                String path = "";
    
                for (Node fib = (Node) elt; fib != null; fib = fib.getParentNode()){                
                    if (fib.getNodeType() == Node.ELEMENT_NODE){
    
                        String cachedParentPath = xpathCache.get(fib);
    
                        if (cachedParentPath != null){
                            path = cachedParentPath + path;
                            break;
                        }
    
                        DOMElement thisparent = (DOMElement) fib;
                        int idx = getElementIdx(thisparent);
                        String xname = thisparent.getTagName();
    
                            if (idx >= 1) xname += "[" + idx + "]";
                            path = "/" + xname + path;
                    }
                }
    
                /* 
                 * here, not only you know the xpath to the elt, you also 
                 * know the xpath to the ancestors of elt. You can leverage
                 * this to cache the ancestor's xpath as well. But I just 
                 * cache the elt for illustration purpose.
                 * 
                 * To compute ancestor's xpath efficiently, maybe you want to 
                 * store xpath using different data structure other than String.
                 * Maybe a Stack of Strings?
                 */
                if (! xpathCache.containsKey(elt)){
                   xpathCache.put (elt, path);
                }
    
                return path;           
            }
    
    private int getElementIdx(DOMElement elt) {
                 Integer count = nodeIndexCache.get(elt);
                 if (count != null){
                   return count;
                 }
                 count = 1;
    
                 LinkedList<Node> siblings = new LinkedList<Node>();
                 for (Node sib = elt.getPreviousSibling(); sib != null; sib =           sib.getPreviousSibling())
                    {
                       siblings.add(sib);
                    }
    
                 int offset = 0;
                 for (Node n : siblings)
                 {
                    nodeIndexCache.put(n, siblings.size() - index);
                    offset ++;
                 }                
    
                /* 
                 * you can improve index caching even further by doing it in the
                 * above for loop.
                 */      
                nodeIndexCache.put(elt, siblings.size()+1);
    
                return count;
    }
    

    看起来你得到了一个随机节点,你必须通过回溯节点的路径来计算 xpath?如果您最终想要实现的是计算所有节点的 xpath,最快的方法是从根节点开始并遍历树,前提是您有对根节点的引用。

    === 旧 ===

    您可以尝试使用基于事件的 XML 解析 API 而不是 DOM。 JVM 带有一个名为SAXParser 的事件解析器,您可以从使用它开始。还有StAX可以试试。

    基于事件的 XML 解析器发出“事件”,因为它进行深度优先遍历,而不是将 XML 解析为内存中的 DOM。因此,基于事件的解析器会访问 XML 的每个元素,发出类似“onOpenTag”、“onClosedTag”和“onAttribute”的事件。通过编写事件处理程序,您可以像这样构建和/或存储元素的路径:

    ...
    currentPath=new Stack();
    
    onOpenTag(String tagName){
       this.currentPath.push("tagName");
    
       if ("Item".equals(tagName)){
          cache.store(convertToPathString(currentPath));
       }
    }
    
    onCloseTag(String tagName){
       this.currentPath.pop();
    }
    

    基于事件的 API 的好处是速度快,并且为大型 XML 节省了大量内存。

    不好的是你必须编写模式代码来获取你想要的数据。

    【讨论】:

    • 问题是我只得到了一个 org.w3c.domElement ...我正在考虑修改这个 domElement 的 ID,这样我就可以使用 dom4j 之类的东西来搜索这个 ID 并返回 xpath?
    • 啊,你唯一给定的 domElement...好吧,那是不同的故事。
    • 我修改了我的答案以说明您可以执行的缓存类型。看看有没有帮助。
    • 是的,这段代码不起作用。向xpathCache.get(fib);投掷空指针
    • 哦,我认为您需要在代码中的某处新建 xpathCache:xpathCache = new HashMap<...>()。顺便我没有测试这段代码,只是写了代码来说明这个想法;)
    猜你喜欢
    • 2013-12-23
    • 2015-03-17
    • 2017-09-06
    • 2018-08-09
    • 1970-01-01
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多