转自:http://www.ibm.com/developerworks/cn/xml/tips/x-tipsaxis/index.html
当您使用 SAX API 时,所有输入都从使用 org.xml.sax.InputSource
类开始。这个类包含在 SAX API 中,并且提供了输入规范(通过类似于文件或 I/O 流的标准 Java 构造),同时还提供一个公用的系统标识。 接着,SAX 在解析时从 InputSource
抽取这些信息,从而能够解析外部实体以及其它特定于文档来源的资源。
类似地,当您对 SAX 使用封装器(类似于 JAXP API)时,可以调用不同方法。最后,解析使用 SAX InputSource
。例如,考虑 清单 1 中所示的代码段,它使用 JAXP 来启动 SAX 解析。
- SAXParserFactory spf = SAXParserFactory.newInstance();
- SAXParser parser = spf.newSAXParser();
- parser.parse(myFile, myHandler);
即使输入了 java.io.File
文件,它也会在被转交给底层 SAX 实现之前转换成 SAX InputSource
。之所以发生这个转换,是因为这个 JAXP 代码最终访问 org.xml.sax.XMLReader
类,该类只为启动解析提供 清单 2 中所示的两个特征符。
- public void parse(InputSource inputSource);
- public void parse(String systemID);
在此基础上,大多数 SAX 解析器实现(如 Apache Xerces)实际上将字符串系统标识转换为 InputSource
,并将它分配给接收 InputSource
的 parse()
版本。无论您如何编码自己的应用程序,SAX 最终都接收 InputSource
来用于解析。但是,并非所有这样处理的方法都同等地好。
为了避免您代码中出现令人不快的意外,最好直接使用 SAX InputSource
类,而不是让 JAXP 或 SAX 为您处理这个任务。因为实现必须要处理每种可能的情况,所以您常常会看到构造 InputSource
实例的代码,类似于 清单 3 中所示。
- InputSource inputSource = new InputSource();
- // May be a null parameter
- inputSource.setByteStream(inputStream);
- // May be a null parameter
- inputSource.setCharacterStream(reader);
- // May be a null parameter
- inputSource.setSystemId(systemId);
- // May be a null parameter
- inputSource.setPublicId(publicId);
- // Derived parameter
- inputSource.setEncoding(encoding);
正如您从注释中看到的,这些方法中的许多被传递了 null
参数。虽然执行这些方法不会花费很多时间,但 XML 解析应用程序中的每一秒都很关键;遗憾的是,这些不执行任何操作的方法浪费了宝贵的时间。通过自行构造InputSource
实例,您可以将这个过程简化为一至两个方法调用,如 清单 4 所示。
- InputSource inputSource = new InputSource(myInputStream);
- inputSource.setSystemId("http://www.oreilly.com");
- inputSource.setEncoding("UTF-8");
我还使用了 setEncoding()
方法来通知 SAX 解析器使用何种编码;在涉及国际化或使用多字节字符集的 XML 应用程序中,这一点很重要。
但是,这里产生了另一个问题:对于字符编码,用手工设置字符编码的编码与所提供的输入流(通过java.io.InputStream
或 java.io.Reader
)所用的编码 不同 ,这种情况很常见。如果这些编码不匹配,就可能发生各种解析问题。要避免这种情况,请始终用 Java InputStream
创建您的 InputSource
,而不要用 Reader
或String
系统标识(这些都是 JAXP API 的可能选项)。当您提供 InputStream
时,SAX 实现将流封装在InputStreamReader
中;然后 SAX 自动从流中检测正确的字符编码。随后,您可以省略 setEncoding()
步骤,再次减少方法调用。结果是应用程序运行更快了,并且字符编码始终正确。