Java: Как ускорить генерацию строки xpath в данном документе w3c dom? - PullRequest
2 голосов
/ 28 июня 2011

У меня есть следующий метод, который берет 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;
        }

Ответы [ 3 ]

3 голосов
/ 04 июля 2011

Я не уверен, генерируете ли вы XPath для нескольких или только для одного узла в каждом документе DOM, но если вы генерируете несколько, вы можете кэшировать выражения, как это предлагают другие.Сложно оценить, но если вы хотите сгенерировать очень много XPath-файлов из одного и того же документа, вы можете также изменить алгоритм, чтобы начать с корневого элемента.И обратите внимание, что вы можете нормализовать текстовые узлы, если у вас их много, но я не уверен в общей производительности;)

Но независимо от того, итерация по узлам DOM действительно быстрая. Но ваша обработка String не , на самом деле это несколько плохо.Переключитесь на один StringBuilder (спасибо, Элвин) вместо вашего текущего подхода (использование + для добавления строк скомпилировано во что-то более сложное, см. Javadoc).Убедитесь, что вы инициализируете его в конструкторе подходящего размера.

На самом деле вам также не нужно проверять имя тега, тип элемента any-name разрешен в XPath.Например, /*[1]/*[2].

3 голосов
/ 28 июня 2011

Ваш код равен O (n ^ 2) в количестве братьев и сестер (то есть, максимальное разветвление дерева).

При любой проблеме DOM лучше всегда избегать использованияDOM!Но я не знаю, является ли это вариантом в вашем случае.

Менее радикальным изменением было бы изменение вашего кода таким образом, чтобы при обходе дочерних элементов узла поддерживалась хэш-карта, содержащая для каждого элементавстреченное имя, количество элементов с таким именем, а затем использовать эту информацию для генерации индекса (индекса), а не обратного отсчета всех предыдущих братьев и сестер.

2 голосов
/ 28 июня 2011

=== Новое - поэтому вам нужно использовать DOM ===

Чтобы ускорить процесс, вы можете сделать кеширование (как предложил другой человек). Обратите внимание, что ваш текущий код вычисляет xpath для одного и того же узла несколько раз (или для каждого узла N вам придется вычислять xpath для N для каждого из дочерних элементов N). Вот что я имею в виду для кэширования:

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 для всех узлов, самый быстрый способ - начать с корневого узла и пройти через дерево, если у вас есть ссылка на корневой узел.

=== СТАРЫЙ ===

Вы можете попробовать использовать API анализа XML на основе событий вместо DOM. JVM поставляется с анализатором событий SAXParser , вы можете начать с него. Существует также StAX , который вы можете попробовать.

Анализатор XML, основанный на событиях, генерирует «события», поскольку он выполняет обход в глубину вместо анализа XML в in-memory-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.

Плохая вещь в том, что вы должны написать код режима, чтобы получить нужные данные.

...