Какой самый эффективный способ памяти использовать XML из JAXP SAX ContentHandler?
У меня есть ситуация, похожая на предыдущий вопрос об эмиссии XML. Я анализирую данные в SAX ContentHandler при сериализации их в поток. Я подозреваю, что решение в связанном вопросе - хотя это именно то, что я ищу с точки зрения API - неэффективно для памяти, поскольку оно включает в себя преобразование идентичности с процессором XSLT. Я хочу, чтобы потребление памяти программой было ограничено, а не увеличивалось с увеличением размера ввода.
Как я могу легко переслать параметры в мои методы ContentHandler в сериализатор, не выполняя акробатические действия для адаптации, например, StAX к SAX, или, что еще хуже, копируя содержимое событий SAX в выходной поток?
Изменить: вот минимальный пример того, что я после. thingIWant
должен просто написать в OutputStream, данный ему. Как я уже сказал, в предыдущем вопросе был TransformerHandler, который дает мне правильный API, но он использует процессор XSLT вместо простой сериализации.
public class MyHandler implements ContentHandler {
ContentHandler thingIWant;
MyHandler(OutputStream outputStream) {
thingIWant = setup(outputStream);
}
public void startDocument() throws SAXException {
// parsing logic
thingIWant.startDocument();
}
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
// parsing logic
thingIWant.startElement(uri, localName, qName, atts);
}
public void characters(char[] ch, int start, int length) throws SAXException {
// parsing logic
thingIWant.characters(ch, start, length);
}
// etc...
}
3 ответа
Первое: не беспокойтесь о преобразовании идентичности; он не создает представление данных в памяти.
Чтобы реализовать свою функциональность "тройник", вы должны создать обработчик контента, который прослушивает поток событий, генерируемых синтаксическим анализатором, и передает их обработчику, предоставленному вам преобразователем. К сожалению, это не так просто, как кажется: анализатор хочет отправлять события в DefaultHandler, в то время как преобразователь хочет читать события из XMLReader. Первый - это абстрактный класс, второй - интерфейс. JDK также предоставляет класс XMLFilterImpl, который реализует все интерфейсы DefaultHandler
, но не расширяется от этого... это то, что вы получаете за включение двух разных проектов в качестве "эталонных реализаций".
Итак, вам нужно написать класс моста между ними:
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLFilterImpl;
/**
* Uses a decorator ContentHandler to insert a "tee" into a SAX parse/serialize
* stream.
*/
public class SaxTeeExample
{
public static void main(String[] argv)
throws Exception
{
StringReader src = new StringReader("<root><child>text</child></root>");
StringWriter dst = new StringWriter();
Transformer xform = TransformerFactory.newInstance().newTransformer();
XMLReader reader = new MyReader(SAXParserFactory.newInstance().newSAXParser());
xform.transform(new SAXSource(reader, new InputSource(src)),
new StreamResult(dst));
System.out.println(dst.toString());
}
private static class MyReader
extends XMLFilterImpl
{
private SAXParser _parser;
public MyReader(SAXParser parser)
{
_parser = parser;
}
@Override
public void parse(InputSource input)
throws SAXException, IOException
{
_parser.parse(input, new XMLFilterBridge(this));
}
// this is an example of a "tee" function
@Override
public void startElement(String uri, String localName, String name, Attributes atts) throws SAXException
{
System.out.println("startElement: " + name);
super.startElement(uri, localName, name, atts);
}
}
private static class XMLFilterBridge
extends DefaultHandler
{
private XMLFilterImpl _filter;
public XMLFilterBridge(XMLFilterImpl myFilter)
{
_filter = myFilter;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException
{
_filter.characters(ch, start, length);
}
// override all other methods of DefaultHandler
// ...
}
}
main
Метод устанавливает трансформатор. Интересно то, что SAXSource
построен вокруг MyReader
, Когда трансформатор будет готов к событиям, он вызовет parse()
метод этого объекта, передавая ему указанный InputSource
,
Следующая часть не очевидна: XMLFilterImpl
следует шаблону Decorator. Преобразователь будет вызывать различные методы установки для этого объекта перед началом преобразования, передавая свои собственные обработчики. Любые методы, которые я не переопределяю (например, startDocument()
) просто позвонит делегату. В качестве примера переопределения, я делаю "анализ" (просто println) в startElement()
, Вы, вероятно, переопределите другие ContentHandler
методы.
И наконец, XMLFilterBridge
это мост между DefaultHandler
а также XmlReader
; это также декоратор, и каждый метод просто вызывает делегат. Я показываю одно переопределение, но вам придется сделать их все.
У меня недавно была похожая проблема. Вот класс, который я написал, чтобы вы поняли:
import java.io.OutputStream;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import org.xml.sax.*;
public class XMLSerializer implements ContentHandler {
static final private TransformerFactory tf = TransformerFactory.newInstance();
private ContentHandler ch;
public XMLSerializer(OutputStream os) throws SAXException {
try {
final Transformer t = tf.newTransformer();
t.transform(new SAXSource(
new XMLReader() {
public ContentHandler getContentHandler() { return ch; }
public DTDHandler getDTDHandler() { return null; }
public EntityResolver getEntityResolver() { return null; }
public ErrorHandler getErrorHandler() { return null; }
public boolean getFeature(String name) { return false; }
public Object getProperty(String name) { return null; }
public void parse(InputSource input) { }
public void parse(String systemId) { }
public void setContentHandler(ContentHandler handler) { ch = handler; }
public void setDTDHandler(DTDHandler handler) { }
public void setEntityResolver(EntityResolver resolver) { }
public void setErrorHandler(ErrorHandler handler) { }
public void setFeature(String name, boolean value) { }
public void setProperty(String name, Object value) { }
}, new InputSource()),
new StreamResult(os));
}
catch (TransformerException e) {
throw new SAXException(e);
}
if (ch == null)
throw new SAXException("Transformer didn't set ContentHandler");
}
public void setDocumentLocator(Locator locator) {
ch.setDocumentLocator(locator);
}
public void startDocument() throws SAXException {
ch.startDocument();
}
public void endDocument() throws SAXException {
ch.endDocument();
}
public void startPrefixMapping(String prefix, String uri) throws SAXException {
ch.startPrefixMapping(prefix, uri);
}
public void endPrefixMapping(String prefix) throws SAXException {
ch.endPrefixMapping(prefix);
}
public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
ch.startElement(uri, localName, qName, atts);
}
public void endElement(String uri, String localName, String qName)
throws SAXException {
ch.endElement(uri, localName, qName);
}
public void characters(char[] ch, int start, int length)
throws SAXException {
this.ch.characters(ch, start, length);
}
public void ignorableWhitespace(char[] ch, int start, int length)
throws SAXException {
this.ch.ignorableWhitespace(ch, start, length);
}
public void processingInstruction(String target, String data)
throws SAXException {
ch.processingInstruction(target, data);
}
public void skippedEntity(String name) throws SAXException {
ch.skippedEntity(name);
}
}
По сути, он перехватывает вызов Transformer для parse() и получает ссылку на его внутренний ContentHandler. После этого класс действует как прокси для зацепленного ContentHandler.
Не очень чисто, но это работает.
Изменить: Включает версию JDK по умолчанию
Наиболее эффективным будет XMLWriter
который реализует ContentHandler
, В двух словах, вы читаете и пишете из буферов ввода-вывода и в них. В DOM4J есть XMLWriter, который используется ниже. Вы можете либо подкласс XMLWriter
или использовать XMLFilter
сделать анализ. я использую XMLFilter
в этом примере. Обратите внимание, что XMLFilter
также ContentHandler
, Вот полный код.
import org.dom4j.io.XMLWriter;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLFilterImpl;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.PrintStream;
public class XMLPipeline {
public static void main(String[] args) throws Exception {
String inputFile = "build.xml";
PrintStream outputStream = System.out;
new XMLPipeline().pipe(inputFile, outputStream);
}
//dom4j
public void pipe(String inputFile, OutputStream outputStream) throws
SAXException, ParserConfigurationException, IOException {
XMLWriter xwriter = new XMLWriter(outputStream);
XMLReader xreader = XMLReaderFactory.createXMLReader();
XMLAnalyzer analyzer = new XMLAnalyzer(xreader);
analyzer.setContentHandler(xwriter);
analyzer.parse(inputFile);
//do what you want with analyzer
System.err.println(analyzer.elementCount);
}
//default JDK
public void pipeTrax(String inputFile, OutputStream outputStream) throws
SAXException, ParserConfigurationException,
IOException, TransformerException {
StreamResult xwriter = new StreamResult(outputStream);
XMLReader xreader = XMLReaderFactory.createXMLReader();
XMLAnalyzer analyzer = new XMLAnalyzer(xreader);
TransformerFactory stf = SAXTransformerFactory.newInstance();
SAXSource ss = new SAXSource(analyzer, new InputSource(inputFile));
stf.newTransformer().transform(ss, xwriter);
System.out.println(analyzer.elementCount);
}
//This method simply reads from a file, runs it through SAX parser and dumps it
//to dom4j writer
public void dom4jNoop(String inputFile, OutputStream outputStream) throws
IOException, SAXException {
XMLWriter xwriter = new XMLWriter(outputStream);
XMLReader xreader = XMLReaderFactory.createXMLReader();
xreader.setContentHandler(xwriter);
xreader.parse(inputFile);
}
//Simplest way to read a file and write it back to an output stream
public void traxNoop(String inputFile, OutputStream outputStream)
throws TransformerException {
TransformerFactory stf = SAXTransformerFactory.newInstance();
stf.newTransformer().transform(new StreamSource(inputFile),
new StreamResult(outputStream));
}
//this analyzer counts the number of elements in sax stream
public static class XMLAnalyzer extends XMLFilterImpl {
int elementCount = 0;
public XMLAnalyzer(XMLReader xmlReader) {
super(xmlReader);
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
super.startElement(uri, localName, qName, atts);
elementCount++;
}
}
}