Как стать автором
Обновить

XPath: ускоряем итерацию по NodeList

Время на прочтение 5 мин
Количество просмотров 16K
При попытке обработки не очень маленького регулярного XML-файла (на самом деле всего лишь около тысячи записей) обнаружил, что итерирование по NodeList вместе с извлечением с помощью XPath начинает существенно тормозить (занимая порядка 2 минут на моём файле), причем тормоза увеличиваются с обработкой каждого следующего узла (node). Эта проблема поднимается также

blog.astradele.com/2006/02/24/slow-xpath-evaluation-for-large-xml-documents-in-java-15

jbwhammie.blogspot.com/2011/02/make-java-xpath-work-on-large-files.html

Как я понял, это некая особенность реализации DOM & XPath в JDK, которая заключается в том, что если мы получили XPath-запросом некий Node из XML, то этот Node остаётся ссылающимся через parent на родительский XML Document. И последующие попытки XPath-запросов к этому Node приводят к тому, что обрабатывается не только сам Node но и весь XML Document. Поэтому логичным является попытка обнулить parent у Node, например, путем удаления этого Node из его родительского Node. Именно такой способ предлагается по ссылкам выше, и он работает. Единственный его недостаток в том, что он изменяет сам XML Document, делая его более не годным к дальнейшему извлечению данных. Я обнаружил, что есть другой способ не меняющий сам XML Document — клонирование Node, т.к., согласно документации, при этом у клона parent = null.

Собственно, описанная проблема и подходы к решению иллюстрируются следующим кодом.

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayInputStream;

public class SlowXPath {
  public static void main(String[] args) throws Exception {
    final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

    final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

    String xmlString = prepareXml(5000);

//    System.out.println(xmlString);
    final Document xmlDoc = documentBuilder.parse(new ByteArrayInputStream(xmlString.getBytes()));

    final XPathFactory xPathFactory = XPathFactory.newInstance();

    final XPathExpression nodeXPath = xPathFactory.newXPath().compile("//node");
    final XPathExpression iXPath = xPathFactory.newXPath().compile("./i/text()");

    final NodeList nodeList = (NodeList) nodeXPath.evaluate(xmlDoc, XPathConstants.NODESET);

    System.out.println("Nodes number=" + nodeList.getLength());

    timeIt("Simple iterate", new Runnable() {
      @Override
      public void run() {
        int sum = 0;

        for (int i = 0; i < nodeList.getLength(); i++) {
          final Node node = nodeList.item(i);
          try {
            final String iStr = (String) iXPath.evaluate(node, XPathConstants.STRING);
            sum += Integer.parseInt(iStr.trim());
          } catch (XPathExpressionException e) {
            e.printStackTrace();
          }
        }

        System.out.println("Sum=" + sum);
      }
    });
    timeIt("Iterate with cloning", new Runnable() {
      @Override
      public void run() {
        int sum = 0;

        for (int i = 0; i < nodeList.getLength(); i++) {
          final Node node = nodeList.item(i).cloneNode(true); // <-- Note cloning here
          try {
            final String iStr = (String) iXPath.evaluate(node, XPathConstants.STRING);
            sum += Integer.parseInt(iStr.trim());
          } catch (XPathExpressionException e) {
            e.printStackTrace();
          }
        }

        System.out.println("Sum=" + sum);
      }
    });
    timeIt("Iterate with detaching node", new Runnable() {
      @Override
      public void run() {
        int sum = 0;

        for (int i = 0; i < nodeList.getLength(); i++) {
          final Node node = nodeList.item(i);

          node.getParentNode().removeChild(node); // <-- Note detaching node

          try {
            final String iStr = (String) iXPath.evaluate(node, XPathConstants.STRING);
            sum += Integer.parseInt(iStr.trim());
          } catch (XPathExpressionException e) {
            e.printStackTrace();
          }
        }

        System.out.println("Sum=" + sum);
      }
    });
  }

  private static String prepareXml(int n) {
    StringBuilder sb = new StringBuilder();

    sb.append("<root>");

    for (int i = 0; i < n; i++) {
      sb.append("<node><i>\n")
          .append(i)
          .append("</i></node>\n");
    }

    sb.append("</root>");

    return sb.toString();
  }

  private static void timeIt(String name, Runnable runnable) {
    long t0 = System.currentTimeMillis();
    runnable.run();
    long t1 = System.currentTimeMillis();

    System.out.println(name + " executed " + ((t1 - t0) / 1000f) + "sec.");
  }
}

* This source code was highlighted with Source Code Highlighter.


А вот результат работы:

Nodes number=5000
Sum=12497500
Simple iterate executed 17.359sec.
Sum=12497500
Iterate with cloning executed 1.047sec.
Sum=12497500
Iterate with detaching node executed 1.031sec.
Теги:
Хабы:
+22
Комментарии 14
Комментарии Комментарии 14

Публикации

Истории

Работа

Java разработчик
359 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн
PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн