如今,XML 在很多性能关键型场景中扮演着重要角色。虽然很多开发人员都知道如何编写 XML 文档、XML 模式或 DTD,但有些人可能还没有认识到,XML 应用程序的性能取决于构造 XML 文档时所作出的一些决定,以及在解析 XML 文档之前,在解析器上设置了哪些特性。
很多开发人员也知道何时使用 SAX,何时使用 DOM API。通常,如果内存不充裕,而应用程序必须处理较大的文档,或者要在内存中创建自己的表示,那么您最好使用 SAX(而不是 DOM)。另一方面,如果应用程序需要随机访问和修改文档数据,想要实现复杂的搜索,或者计划多次遍历一个文档树,则可以尽量使用 DOM。在本文中,我们将解释哪些 SAX 或 DOM 操作和特性会影响应用程序的性能,并描述如何编写性能最佳的应用程序。
编写 XML 文档
负责编写 XML 文档的开发人员可以做各种事情来提高 XML 应用程序的性能。
每个 XML 文档都可以在 XML 声明中指定一种字符编码。为了达到最佳性能,在编写 XML 文档时应使用 US ASCII ( "US-ASCII" ) 作为编码。用 ASCII 字符编写的文档解析起来是最快的,因为每个字符都肯定是单字节的,可以直接映射到对应的 Unicode 值。如果文档是用 UTF-8 编码的,但是只包含 ASCII 字符,那么有些解析器(例如 Xerces2)处理这类文档时采用的方式与处理用 US-ASCII 编码的等价 XML 文档时采用的方式几乎相同。对于包含 ASCII 以外 的 Unicode 字符的文档,解析器必须为每个字符读取和转换多字节序列。这种转换会损失一定的性能。因为每个字符都规定使用两个字节,同时假设没有代替的字符,所以 UTF-16 编码在一定程度上减轻了这种性能损失。然而,如果使用 UTF-16 的话,原始文档的大小便要加倍,这样的文档解析起来要花更多的时间。
通过减少文档中的新行数目和空格,也可以提高性能。通常,为了编辑方便,开发人员会将文档组织成行 —— 例如,使用回车( #xD )和换行( #xA )。XML 解析器必须将双字符序列 #xD #xA 以及所有 #xD (后面没有跟 #xA )转换成单个的 #xA 字符。这种转换并非没有代价。对于解析过程的总体性能影响取决于文档中与新行数目相关的字符数。对于空格的使用也是如此。当您将空格添加到文档中时,解析器就要处理更多的字符,从而最终影响解析过程的性能。
另外,除非绝对有必要,否则应该避免在应用程序中使用名称空间(namespace)。处理启用了名称空间特性的文档会降慢对整个文档的处理。解析器不仅要处理名称空间的声明、验证它们的正确性,而且还要确保 XML 文档是名称空间格式良好的。
对于不需要进行验证的应用程序,它们的文档中应该 不包括 <!DOCTYPE...> 这一行。根据 XML 规范,验证处理程序(例如 Xerces2)必须处理内部和外部 DTD 子集,以获得关于默认属性、属性类型等信息。即使禁用了验证特性,该处理程序也仍然会处理 DTD。
当需要对应用程序进行验证时,应记住,处理和验证 DTD 所花费的代价通常比处理和验证 W3C XML Schema 的要小一些。此外,还应避免使用大量的外部实体 —— 例如外部 DTD 或导入的 XML 模式,因为打开和读取文件是代价很高的操作。同时要避免使用太多的默认属性,因为这会增加验证时间。XML Schema 的 redefine 结构和 identity 约束也应当避免,因为两者都会影响验证过程。
常见 SAX 性能提示
对于更消耗内存的 API(例如 DOM),选择 SAX 可以提高应用程序的性能。但是,您还可以做很多事情来进一步提高性能。试一试下面这些可以提高 SAX 应用程序性能的提示:
限制 XML 名称为内部化字符串。
在多个处理程序之间 切换内容处理程序来处理大型文档。
用实体分解器(resolver) 装载外部实体。
避免处理外部实体。
字符串内部化
SAX 指定了一个由特性 URI http://xml.org/sax/features/string-interning 标识的特性。在将该特性设为 true 时,它会通过调用 java.lang.String.intern() 来指示解析器以内部化字符串的形式报告 XML 名称(例如元素和属性的名称)以及名称空间 URI。
为了加快字符串等同性测试的速度,可以打开该特性。这里不需要调用一个字符一个字符地进行比较的 equals() 函数,相反,您可以通过引用将解析器报告的名称与字符串常量进行比较。如果使用解析器报告的 XML 名称作为 hash 表的键,那么,当该表调用 java.lang.String 的 hashCode 方法时,内部化字符串就可以缩短查找时间。虽然在 Javadoc 中没有指定,这个 hashCode 方法的实现通常会在计算 hash 码值之后,将其缓存在对象中。一旦计算出了 hash 码,想获得一个内部化字符串的 hash 码实际上就轻而易举了。
有些解析器的实现可能不支持字符串内部化特性。Xerces2 使用内部化字符串来获得更快的比较速度,所以这一特性永远是开着的。