从JAXP SAX ContentHandler发出XML的内存效率最高的方法是什么?

时间:2009-12-29 23:12:10

标签: java xml-serialization sax jaxp

我的情况类似于an earlier question about emitting XML。我正在分析SAX ContentHandler中的数据,同时将其序列化为流。我怀疑链接问题中的解决方案 - 虽然它正是我在API方面寻找的 - 不是内存效率,因为它涉及使用XSLT处理器进行身份转换。我希望程序的内存消耗有限,而不是随着输入大小的增长而增长。

如何轻松地将我的ContentHandler方法的参数转发给序列化程序,而无需进行杂技以适应例如StAX to 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 个答案:

答案 0 :(得分:4)

我最近遇到了类似的问题。这是我写给你的东西想要的东西:

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的代理。

不是很干净,但是有效。

答案 1 :(得分:2)

首先:不要担心身份转换;它不会构建数据的内存表示。

要实现“tee”功能,您必须创建一个内容处理程序,用于侦听解析器生成的事件流,并将它们传递给变换器为您提供的处理程序。不幸的是,这并不像听起来那么容易:解析器想要将事件发送到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遵循装饰器模式。变换器将在开始转换之前调用此对象上的各种setter方法,并传递其自己的处理程序。我不覆盖的任何方法(例如,startDocument())将只调用委托。作为示例覆盖,我正在startElement()中进行“分析”(只是一个println)。您可能会覆盖其他ContentHandler方法。

最后,XMLFilterBridgeDefaultHandlerXmlReader之间的桥梁;它也是一个装饰器,每个方法都只调用委托。我展示了一个覆盖,但你必须全部完成。

答案 2 :(得分:1)

编辑:包含默认的JDK版本

最有效的是实现XMLWriter的{​​{1}}。简而言之,您正在从IO缓冲区读取和写入。 DOM4J中有一个XMLWriter,正在下面使用。您可以子类ContentHandler或使用XMLWriter进行分析。我在这个例子中使用XMLFilter。请注意,XMLFilter也是XMLFilter。这是完整的代码。

ContentHandler