Статьи

Эффективная память XML Обработка не только с DOM

Как я могу эффективно анализировать большие XML-файлы, размер которых может быть несколько ГБ? С SAX ? Хм, ну да: можно! Но это несколько уродливо. Если вы предпочитаете лучший подход с поддержкой maintable, вам определенно стоит попробовать joost, который не загружает весь XML-файл в память, но очень похож на xslt.

Но как я могу сделать это с DOM или даже лучше с dom4j , если у вас всего 50 МБ или даже меньше ОЗУ? Ну, это не всегда возможно, но при некоторых обстоятельствах вы можете сделать это с помощью небольшого вспомогательного класса. Читать дальше!

У вас есть XML-файл

<products>
<product id="1"> CONTENT1 .. </product>
<product id="2"> CONTENT2 .. </product>
<product id="3"> CONTENT3 .. </product>
...
</products>

Затем вы можете проанализировать продукт по продукту через:

List<String> idList = new ArrayList<String>();
ContentHandler productHandler =
new GenericXDOMHandler("/products/product") {
public void writeDocument(String localName, Element element)
throws Exception {
// use DOM here
String id = element.getAttribute("id");
idList.add(id)
}
}
GenericXDOMHandler.execute(new File(inputFile), productHandler);

Как это работает? Каждый раз, когда обработчик SAX обнаруживает элемент <product>, он считывает дерево продуктов (которое довольно мало) в ОЗУ и вызывает функцию writeDocument. Технически мы добавили слушателя ко всем элементам продукта с этим и ждем «событий» от нашего GenericXDOMHandler. Код был разработан для моего проекта xvantage, но также используется в рабочем коде для больших файлов:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
* License: http://en.wikipedia.org/wiki/Public_domain
* This software comes without WARRANTY about anything! Use it at your own risk!
*
* Reads an xml via sax and creates an Element object per document.
*
* @author Peter Karich, peathal 'at' yahoo 'dot' de
*/
public abstract class GenericXDOMHandler extends DefaultHandler {

private Document factory;
private Element current;
private List<String> rootPath;
private int depth = 0;

public GenericXDOMHandler(String forEachDocument) {
rootPath = new ArrayList<String>();
for (String str : forEachDocument.split("/")) {
str = str.trim();
if (str.length() > 0)
rootPath.add(str);
}

if (rootPath.size() < 2)
throw new UnsupportedOperationException("forEachDocument"+
+" must have at least one sub element in it."
+ "E.g. /root/subPath but it was:" + rootPath);
}

@Override
public void startDocument() throws SAXException {
try {
factory = DocumentBuilderFactory.newInstance().
newDocumentBuilder().newDocument();
} catch (Exception e) {
throw new RuntimeException("can't get DOM factory", e);
}
}

@Override
public void startElement(String uri, String local,
String qName, Attributes attrs) throws SAXException {

// go further only if we add something to our sub tree (defined by rootPath)
if (depth + 1 < rootPath.size()) {
current = null;
if (rootPath.get(depth).equals(local))
depth++;

return;
} else if (depth + 1 == rootPath.size()) {
if (!rootPath.get(depth).equals(local))
return;
}

if (current == null) {
// start a new subtree
current = factory.createElement(local);
} else {
Element childElement = factory.createElement(local);
current.appendChild(childElement);
current = childElement;
}

depth++;

// Add every attribute.
for (int i = 0; i < attrs.getLength(); ++i) {
String nsUri = attrs.getURI(i);
String qname = attrs.getQName(i);
String value = attrs.getValue(i);
Attr attr = factory.createAttributeNS(nsUri, qname);
attr.setValue(value);
current.setAttributeNodeNS(attr);
}
}

@Override
public void endElement(String uri, String localName,
String qName) throws SAXException {

if (current == null)
return;

Node parent = current.getParentNode();

// leaf of subtree
if (parent == null)
current.normalize();

if (depth == rootPath.size()) {
try {
writeDocument(localName, current);
} catch (Exception ex) {
throw new RuntimeException("Exception"+
+" while writing one element of path:" + rootPath, ex);
}
}

// climb up one level
current = (Element) parent;
depth--;
}

@Override
public void characters(char buf[], int offset, int length)
throws SAXException {
if (current != null)
current.appendChild(factory.createTextNode(
new String(buf, offset, length)));
}

public abstract void writeDocument(String localName, Element element)
throws Exception {
}

public static void execute(File inputFile,
ContentHandler handler)
throws SAXException, FileNotFoundException, IOException {

execute(new FileInputStream(inputFile), handler);
}

public static void execute(InputStream input,
ContentHandler handler)
throws SAXException, FileNotFoundException, IOException {

XMLReader xr = XMLReaderFactory.createXMLReader();
xr.setContentHandler(handler);
InputSource iSource = new InputSource(new InputStreamReader(input, "UTF-8"));
xr.parse(iSource);
}
}

PS: должно быть просто адаптировать этот класс к вашим потребностям; например, используя dom4j вместо DOM. Вы можете даже зарегистрировать несколько путей, а не только один rootPath через BindingTree. Для реализации этого взгляните на мой проект xvantage .

PPS: Если вы хотите обрабатывать выражения xpath в методе writeDocument, убедитесь, что это не является узким местом производительности с обычным механизмом xpath! Потому что метод можно вызывать несколько раз. В моем случае у меня было несколько тысяч документов, но Jaxen решил эту проблему!

PPPS: Если вы хотите обрабатывать xml-запись и чтение («сериализацию xml») из классов Java, посмотрите этот список !

 

От http://karussell.wordpress.com/2010/04/29/memory-efficient-xml-processing-not-only-with-dom/