Java: Найефективніший метод перебору всіх елементів в org.w3c.dom.Document?


74

Який найефективніший спосіб переглядати всі елементи DOM в Java?

Щось подібне, але для кожного окремого елемента DOM на поточному 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;
}

Рекурсивне виклик Node.getChildNodes? download.oracle.com/javase/6/docs/api/org/w3c/dom/…
Венс Маверік

Я думаю, що цікаво, що питання задало найбільш ефективний метод для перебору всіх елементів a Document, але жодна з відповідей не проводила жодних перевірок ефективності, і лише згадка про ефективність була "я думаю" чи подібні припущення.
Гаррет Вілсон

Відповіді:


129

В основному у вас є два способи перегляду всіх елементів:

1. Використання рекурсії (найпоширеніший спосіб, на мою думку):

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. Уникнення рекурсії за допомогою getElementsByTagName()методу з *параметром:

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());
        }
    }
}

Я думаю, що ці способи є ефективними.
Сподіваюся, це допомагає.


11
Передаючи індекс ітерації як аргумент рекурсивній функції, ви можете зробити його хвостово-рекурсивним, який оптимізований компілятором, щоб уникнути переповнення стека.
хачик

128
Я думаю, що пізно, щоб уникнути переповнення стека. Ви вже тут.
braden

1
Що змушує вас думати, що створення списку вузлів для всього документа є ефективним? Це означає майже копіювати весь документ. Або в NodeListоптимізації послідовних викликів ховається якась відстрочена оцінка item?
закінчується

1
@ceving NodeList - це інтерфейс. Реалізації можуть вільно робити просунуті речі. Реалізація елемента (n) в org.apache.xerces.dom.ParentNode включає кеш, але він використовується для пришвидшення пошуку, а не для економії пам'яті.
Райан

Переходьте до відповіді №2, але змініть цикл for на читання: for (int i = 0, len = nodeList.getLength (); i <len; i ++)
Ендрю

37

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

перейти

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

бути ефективнішими.

Другий спосіб відповіді яванни може бути найкращим, оскільки він, як правило, використовує більш плоску, передбачувану модель пам'яті.


1
Вам потрібно принаймні 50 балів для коментування. У мене була та ж проблема і я відповів, бо не міг коментувати. Майте якусь підтримку;)
nyaray

Наведене вище рішення уникнення рекурсії заважає програмі використовувати більше стекової пам'яті на основі даних. Кожен крок у рекурсії штовхає більше даних у стек.
Ендрю

2

Я також нещодавно натрапив на цю проблему. Ось моє рішення. Я хотів уникнути рекурсії, тому використовував цикл while.

Через додавання та видалення у довільних місцях у списку я пішов із LinkedListреалізацією.

/* 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;
  }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.