При попытке обработки не очень маленького регулярного 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.
Собственно, описанная проблема и подходы к решению иллюстрируются следующим кодом.
А вот результат работы:
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.