Java: Die effizienteste Methode, um alle Elemente in einem org.w3c.dom.Document zu durchlaufen?

74

Was ist der effizienteste Weg, um alle DOM-Elemente in Java zu durchlaufen?

So etwas aber für jedes einzelne DOM-Element aktuell org.w3c.dom.Document?

for(Node childNode = node.getFirstChild(); childNode!=null;){
    Node nextChild = childNode.getNextSibling();
    // Do something with childNode, including move or delete...
    childNode = nextChild;
}
KJW
quelle
Rekursiver Aufruf von Node.getChildNodes? download.oracle.com/javase/6/docs/api/org/w3c/dom/…
Vance Maverick
Ich finde es interessant, dass die Frage die effizienteste Methode zum Durchlaufen aller Elemente von a stellte Document, aber keine der Antworten führte zu Effizienzprüfungen, und die einzige Erwähnung der Effizienz war "Ich denke" oder ähnliche Vermutungen.
Garret Wilson

Antworten:

129

Grundsätzlich haben Sie zwei Möglichkeiten, alle Elemente zu durchlaufen:

1. Rekursion verwenden (die häufigste Art, wie ich denke):

public static void main(String[] args) throws SAXException, IOException,
        ParserConfigurationException, TransformerException {

    DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
        .newInstance();
    DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
    Document document = docBuilder.parse(new File("document.xml"));
    doSomething(document.getDocumentElement());
}

public static void doSomething(Node node) {
    // do something with the current node instead of System.out
    System.out.println(node.getNodeName());

    NodeList nodeList = node.getChildNodes();
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node currentNode = nodeList.item(i);
        if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
            //calls this method for all the children which is Element
            doSomething(currentNode);
        }
    }
}

2. Vermeiden Sie eine Rekursion mit der getElementsByTagName()Methode *als Parameter:

public static void main(String[] args) throws SAXException, IOException,
        ParserConfigurationException, TransformerException {

    DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
            .newInstance();
    DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
    Document document = docBuilder.parse(new File("document.xml"));

    NodeList nodeList = document.getElementsByTagName("*");
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node node = nodeList.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            // do something with the current element
            System.out.println(node.getNodeName());
        }
    }
}

Ich denke, diese Wege sind beide effizient.
Hoffe das hilft.

Javanna
quelle
11
Wenn Sie den Iterationsindex als Argument an die rekursive Funktion übergeben, können Sie ihn endrekursiv machen, was vom Compiler optimiert wird, um einen Stapelüberlauf zu vermeiden.
Khachik
128
Ich denke, es ist zu spät, um einen Stapelüberlauf zu vermeiden. Du bist schon hier.
Braden
1
Was lässt Sie denken, dass die Erstellung einer Knotenliste für das gesamte Dokument effizient ist? Dies bedeutet, dass fast das gesamte Dokument kopiert wird. Oder ist bei der NodeListOptimierung von sequentiellen Aufrufen eine Art verzögerte Auswertung verborgen item?
7.
1
@ceving NodeList ist eine Schnittstelle. Implementierungen sind frei, um fortgeschrittene Dinge zu tun. Die Implementierung von item (n) in org.apache.xerces.dom.ParentNode enthält einen Cache, wird jedoch verwendet, um die Suche zu beschleunigen und nicht um Speicherplatz zu sparen.
Ryan
Fahren Sie mit Antwort 2 fort, aber ändern Sie die for-Schleife in: for (int i = 0, len = nodeList.getLength (); i <len; i ++)
Andrew
37

for (int i = 0; i < nodeList.getLength(); i++)

ändern

for (int i = 0, len = nodeList.getLength(); i < len; i++)

effizienter sein.

Die zweite Art der Javanna-Antwort ist möglicherweise die beste, da sie tendenziell ein flacheres, vorhersehbares Speichermodell verwendet.

Andrew
quelle
1
Zum Kommentieren benötigen Sie mindestens 50 Wiederholungen. Ich hatte das gleiche Problem und antwortete, weil ich keinen Kommentar abgeben konnte. Haben Sie etwas Upvote-Hilfe;)
Nyaray
Die oben beschriebene Lösung zur Vermeidung von Rekursionen verhindert, dass das Programm basierend auf den Daten mehr Stapelspeicher verwendet. Jeder Schritt in der Rekursion schiebt mehr Daten in den Stapel.
Andrew
2

Ich bin kürzlich auch über dieses Problem gestolpert. Hier ist meine Lösung. Ich wollte eine Rekursion vermeiden, also habe ich eine while-Schleife verwendet.

Aufgrund des Hinzufügens und Entfernens an beliebigen Stellen in der Liste habe ich mich für die LinkedListImplementierung entschieden.

/* traverses tree starting with given node */
  private static List<Node> traverse(Node n)
  {
    return traverse(Arrays.asList(n));
  }

  /* traverses tree starting with given nodes */
  private static List<Node> traverse(List<Node> nodes)
  {
    List<Node> open = new LinkedList<Node>(nodes);
    List<Node> visited = new LinkedList<Node>();

    ListIterator<Node> it = open.listIterator();
    while (it.hasNext() || it.hasPrevious())
    {
      Node unvisited;
      if (it.hasNext())
        unvisited = it.next();
      else
        unvisited = it.previous();

      it.remove();

      List<Node> children = getChildren(unvisited);
      for (Node child : children)
        it.add(child);

      visited.add(unvisited);
    }

    return visited;
  }

  private static List<Node> getChildren(Node n)
  {
    List<Node> children = asList(n.getChildNodes());
    Iterator<Node> it = children.iterator();
    while (it.hasNext())
      if (it.next().getNodeType() != Node.ELEMENT_NODE)
        it.remove();
    return children;
  }

  private static List<Node> asList(NodeList nodes)
  {
    List<Node> list = new ArrayList<Node>(nodes.getLength());
    for (int i = 0, l = nodes.getLength(); i < l; i++)
      list.add(nodes.item(i));
    return list;
  }
Mike
quelle