为啥 SAXParser 在引发事件之前会读取这么多内容?

Posted

技术标签:

【中文标题】为啥 SAXParser 在引发事件之前会读取这么多内容?【英文标题】:Why does SAXParser read so much before throwing events?为什么 SAXParser 在引发事件之前会读取这么多内容? 【发布时间】:2016-01-18 23:22:08 【问题描述】:

场景:我正在通过极慢的网络接收一个巨大的 xml 文件,所以我希望尽早开始过度处理。因此,我决定使用 SAXParser。

我希望在一个标签完成后我会得到一个事件。

以下测试说明了我的意思:

@Test
public void sax_parser_read_much_things_before_returning_events() throws Exception
    String xml = "<a>"
               + "  <b>..</b>"
               + "  <c>..</c>"
                  // much more ...
               + "</a>";

    // wrapper to show what is read
    InputStream is = new InputStream() 
        InputStream is = new ByteArrayInputStream(xml.getBytes());

        @Override
        public int read() throws IOException 
            int val = is.read();
            System.out.print((char) val);
            return val;
        
    ;

    SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
    parser.parse(is, new DefaultHandler()
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException 
            System.out.print("\nHandler start: " + qName);
        

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException 
            System.out.print("\nHandler end: " + qName);
        
    );

我包装了输入流以查看读取的内容以及事件发生的时间。

我的预期是这样的:

<a>                    <- output from read()
Handler start: a
<b>                    <- output from read()
Handler start: b
</b>                   <- output from read()
Handler end: b
...

不幸的是,结果如下:

<a>  <b>..</b>  <c>..</c></a>        <- output from read()
Handler start: a
Handler start: b
Handler end: b
Handler start: c
Handler end: c
Handler end: a

我的错误在哪里,我怎样才能得到预期的结果?

编辑:

第一件事是他试图检测文档版本,这会导致扫描所有内容。使用文档版本,他介于两者之间(但不是我期望的) 他“想要”读取例如 1000 个字节和块这么长时间是不行的,因为此时流可能不包含这么多。 我在 XMLEntityManager 中找到了缓冲区大小: public static final int DEFAULT_BUFFER_SIZE = 8192; public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64; public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 1024;

【问题讨论】:

我认为您应该尝试一个错误的测试文件-我怀疑缓冲读取在开始处理之前有效地读取了您的整个文件,因为它会以(例如)1k 块或其他方式缓冲文件-如果你使用一个大文件,你可能会得到更像你期望的东西。 【参考方案1】:

您似乎对 I/O 的工作方式做出了错误的假设。 XML 解析器与大多数软件一样,会以块的形式请求数据,因为从流中请求单个字节会导致性能灾难。

这并不意味着缓冲区必须在读取尝试返回之前完全填满。只是,ByteArrayInputStream 无法模拟网络InputStream 的行为。您可以通过覆盖 read(byte[], int, int) 并且不返回完整的缓冲区来轻松解决该问题,但是,例如每个请求都有一个字节:

@Test
public void sax_parser_read_much_things_before_returning_events() throws Exception
    final String xml = "<a>"
               + "  <b>..</b>"
               + "  <c>..</c>"
                  // much more ...
               + "</a>";

    // wrapper to show what is read
    InputStream is = new InputStream() 
        InputStream is = new ByteArrayInputStream(xml.getBytes());

        @Override
        public int read() throws IOException 
            int val = is.read();
            System.out.print((char) val);
            return val;
        
        @Override
        public int read(byte[] b, int off, int len) throws IOException 
            return super.read(b, off, 1);
        
    ;

    SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
    parser.parse(is, new DefaultHandler()
        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException 
            System.out.print("\nHandler start: " + qName);
        

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException 
            System.out.print("\nHandler end: " + qName);
        
    );

这将打印出来

<a>  
Handler start: a<b>
Handler start: b..</b>
Handler end: b  <c>
Handler start: c..</c>
Handler end: c</a>
Handler end: a?

显示,XML 解析器如何适应来自InputStream 的数据可用性。

【讨论】:

read(byte[], int, int)可以简化为return super.read(b, off, 1);【参考方案2】:

在内部,SAX 解析器很可能已将您的 InputStream 包装在 BufferedReader 中或使用某种缓冲。否则它会从输入中读取单个字节,这会真正影响性能。

所以您看到的是解析器从输入中读取一个块,然后处理该部分,发出 SAX 事件,等等......

【讨论】:

以上是关于为啥 SAXParser 在引发事件之前会读取这么多内容?的主要内容,如果未能解决你的问题,请参考以下文章

TreeViewItem 中未引发“MouseLeftButtonDown”事件,为啥?

为啥 GridView 中的 LinkBut​​ton 不会引发其 OnClick 事件?

一个例子,变量提升和函数提升就是这么简单!

为啥 Android 套接字定期读取这么慢?

为啥我在 Node 的 HTTP 服务器上收到这么多连接事件?

XML编程总结——使用SAX接口操作xml