Pull to refresh

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

Java
При попытке обработки не очень маленького регулярного 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.
Tags:javaxpathxml
Hubs: Java
Total votes 22: ↑22 and ↓0 +22
Views14.9K

Popular right now