2 votes

Java: Comment accélérer la génération de chaînes xpath sur un document dom w3c donné ?

J'ai la méthode suivante qui prend un document org.w3c.dom et génère une chaîne xpath absolue.

Je remarque que cela prend beaucoup de temps pour parcourir des centaines d'éléments sur une page.

Y a-t-il un moyen d'accélérer le processus ou une approche différente peut-être ?

Note importante : Je dispose uniquement du document 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;
        }

3voto

Michael Kay Points 52194

Votre code est O(n^2) dans le nombre de frères et sœurs (c'est-à-dire le nombre maximum de descendants de l'arbre).

Pour n'importe quel problème lié au DOM, une meilleure approche est toujours d'éviter d'utiliser le DOM ! Mais je ne sais pas si c'est une option dans votre cas.

Un changement moins radical serait de modifier votre code pour qu'il conserve, en parcourant les enfants d'un nœud, une table de hachage contenant pour chaque nom d'élément rencontré, le nombre d'éléments avec ce nom, et ensuite utiliser ces informations pour générer l'indice plutôt que de compter en arrière à travers tous les précédents frères et sœurs.

3voto

ThomasRS Points 5705

Je ne suis pas sûr si vous générez des XPaths pour plusieurs nœuds ou juste un seul dans chaque document DOM, mais si vous en générez plusieurs, vous pouvez mettre en cache les expressions comme suggéré par d'autres. Difficile à estimer, mais si vous voulez générer beaucoup de XPaths à partir du même document, vous pouvez tout aussi bien inverser l'algorithme pour commencer par l'élément racine. Et notez que vous pouvez normaliser les nœuds texte si vous en avez beaucoup, mais je ne suis pas sûr des performances globales ;)

Mais quoi qu'il en soit, l'itération sur les nœuds DOM est vraiment rapide. Mais la manipulation de chaînes de caractères ne l'est pas, en fait c'est plutôt mauvais. Passez à un seul StringBuilder (merci, Alvin) au lieu de votre approche actuelle (l'utilisation de + pour ajouter des chaînes est compilée en quelque chose de plus compliqué, voir la javadoc). Assurez-vous de l'initialiser avec une bonne taille dans le constructeur.

Vous n'avez pas vraiment besoin de vérifier le nom de la balise non plus, tout type d'élément avec n'importe quel nom est autorisé en XPath. Comme /*[1]/*[2] par exemple.

2voto

Alvin Points 3991

\=== Nouveau - Donc vous devez utiliser DOM ===

Pour accélérer les choses, vous pouvez utiliser la mise en cache (comme l'a suggéré l'autre personne). Remarquez que votre code actuel calcule le xpath pour le même nœud plusieurs fois (ou chaque nœud N vous devrez calculer le xpath pour N pour chacun des enfants de N). Voici ce que j'ai en tête pour la mise en cache:

HashMap xpathCache;
HashMap 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;
                }
            }

            /* 
             * ici, non seulement vous connaissez le xpath de elt, 
             * mais vous connaissez également le xpath des ancêtres de elt. 
             * Vous pouvez exploiter cela pour mettre en cache le xpath 
             * des ancêtres également. Mais je mets juste en cache 
             * le elt à titre d'illustration.
             * 
             * Pour calculer efficacement le xpath des ancêtres, peut-être 
             * voulez-vous stocker le xpath en utilisant une structure 
             * de données différente autre qu'une chaîne de caractères. 
             * Peut-être une pile de chaînes ?
             */
            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 siblings = new LinkedList();
             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 ++;
             }                

            /* 
             * vous pouvez améliorer encore davantage la mise en cache 
             * de l'index en le faisant dans la boucle for ci-dessus.
             */      
            nodeIndexCache.put(elt, siblings.size()+1);

            return count;
}

On dirait que vous avez un nœud aléatoire et vous devez calculer le xpath en remontant le chemin du nœud ? Si ce que vous voulez finalement réaliser est de calculer le xpath de tous les nœuds, le moyen le plus rapide est de commencer avec le nœud racine et de traverser l'arbre, à condition d'avoir une référence au nœud racine.

\=== ANCIEN ===

Vous pouvez essayer d'utiliser une API d'analyse XML basée sur des événements au lieu de DOM. JVM est livré avec un analyseur d'événements appelé SAXParser, vous pouvez commencer par utiliser celui-ci. Il y a aussi StAX que vous pouvez essayer.

L'analyseur XML basé sur des événements émet des "événements" lorsqu'il effectue une traversée en profondeur au lieu de parser XML en DOM en mémoire. Ainsi, l'analyseur basé sur des événements visite chaque élément de votre XML, émet des événements comme "onOpenTag", "onClosedTag" et "onAttribute". En écrivant un gestionnaire d'événements, vous pouvez construire et/ou stocker les chemins des éléments de cette manière :

...
currentPath=new Stack();

onOpenTag(String tagName){
   this.currentPath.push("tagName");

   if ("Item".equals(tagName)){
      cache.store(convertToPathString(currentPath));
   }
}

onCloseTag(String tagName){
   this.currentPath.pop();
}

L'aspect positif de l'API basée sur des événements est qu'elle est rapide et économise beaucoup de mémoire pour les gros fichiers XML.

L'aspect négatif est que vous devez écrire plus de code pour obtenir les données souhaitées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X