xml应用程序下载
如今,XML在许多关键性能的场景中扮演着重要角色。 尽管许多开发人员知道如何编写XML文档,XML模式或DTD,但有些开发人员可能没有意识到XML应用程序的性能取决于您在构建XML文档时所做的选择以及在解析XML文档之前在解析器上设置的功能。 。
许多开发人员还知道何时使用SAX和何时使用DOM API。 通常,当应用程序必须处理大型文档或在内存中创建自己的表示形式(而不是DOM)时,在需要考虑内存的情况下,最好使用SAX。 另一方面,在您的应用程序需要随机访问和修改文档数据,想要实现复杂的搜索或计划多次遍历文档树的情况下,最好使用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
字符。 此翻译不是免费的。 对解析的总体性能影响取决于文档中的字符数(相对于新行数)。 这也适用于空白使用。 当您在文档中添加空格时,解析器将处理更多字符,这最终会影响解析性能。
除非绝对必要,否则还应避免在应用程序中使用名称空间。 在启用了名称空间功能的情况下处理文档可能会减慢整个文档的处理速度。 解析器不仅处理名称空间声明,验证其正确性,而且还确保XML文档的格式正确。
不需要验证的应用程序不应该包括<!DOCTYPE...>
在他们的证件一致。 根据XML规范,诸如Xerces2之类的验证处理器必须处理内部和外部DTD子集,以获取有关默认属性,属性类型等的信息。 即使验证功能已关闭,处理器也将处理DTD。
当应用程序需要验证时,请记住,与DTD相比,处理和验证通常比对W3C XML Schema进行处理和验证便宜。 此外,您应该避免使用很多外部实体(例如外部DTD或导入的XML模式),因为打开和读取文件是一项昂贵的操作。 还应避免使用许多默认属性,因为这会增加验证时间。 还应避免使用XML Schema的redefine
构造和identity
约束,因为两者都可能影响验证过程的持续时间。
通用SAX性能提示
在使用内存更密集的API(例如DOM)时选择SAX本身可能会提高应用程序的性能; 您可以做很多事情来最大化它。 请尝试以下技巧以提高您的SAX应用程序的性能:
字符串内部化
SAX指定由功能URI http://xml.org/sax/features/string-interning
标识的功能。 设置为true时,它指示解析器将XML名称(例如元素和属性的名称)和名称空间URI报告为通过调用java.lang.String.intern()
插入的内部化字符串。
要加速字符串相等性测试,请打开此功能。 不必调用equals()
来逐个字符地比较字符串,而是可以通过引用将解析器报告的名称与字符串常量进行比较。 如果将解析器报告的XML名称用作哈希表的键,则如果表调用java.lang.String
的hashCode
方法,则内部化的字符串应会缩短查找时间。 尽管Javadoc中未指定,但此hashCode
方法的实现通常在计算哈希码值之后将其缓存在对象中。 哈希码计算一次后,获取内部化字符串的哈希码基本上是免费的。
某些解析器实现可能不支持字符串内部化功能。 Xerces2使用内部化的字符串进行更快的比较,因此此功能始终处于启用状态。
切换内容处理程序
如果您处理大量XML词汇表,则可能在回调方法中发现大量if
和else
语句。 如SAX规范中所述,在解析过程中的任何时候都可以注册一个新的内容处理程序。 通过对文档的不同部分使用不同的内容处理程序,可以减少回调方法的复杂性和长度。 清单2中所示的类演示了如何在多个处理程序之间拆分清单1中所示的文档处理。
清单1.示例XML文档
<?xml version="1.0" encoding="US-ASCII"?>
<!DOCTYPE root [
<!ELEMENT root (child*)>
<!ELEMENT child (#PCDATA)>
]>
<root><child/></root>
清单2.使用多个内容处理程序
public class MultipleHandlersExample {
private XMLReader reader;
private ContentHandler docHandler;
private ContentHandler rootContentHandler;
private ContentHandler childContentHandler;
...
public void parse (String uri) throws SAXException, IOException {
reader.setContentHandler(docHandler);
reader.parse(uri);
}
public class DocHandler extends DefaultHandler {
public void startElement(String uri, String localName,
String qName, Attributes atts) {
if ("root".equals(qName)) {
// process root
reader.setContentHandler(rootContentHandler);
}
else {} // error: only root expected here
}
}
public class RootContentHandler extends DefaultHandler {
public void startElement(String uri, String localName,
String qName, Attributes atts) {
if ("child".equals(qName)) {
// process child
reader.setContentHandler(childContentHandler);
}
else {} // error: only child expected here
}
public void endElement(String uri, String localName,
String qName) {
// end of root, set content handler for document
reader.setContentHandler(docHandler);
}
}
public class ChildContentHandler extends DefaultHandler {
public void startElement(String uri, String localName,
String qName, Attributes atts) {
// error: no element content expected here
}
public void endElement(String uri, String localName,
String qName) {
// end of child, set content handler for root
reader.setContentHandler(rootContentHandler);
}
}
}
报告特定元素(例如上例中的root
或child
)后,您可以注册一个处理程序以处理该元素的内容。 在元素的结尾,您将还原父元素的内容处理程序。 对于比清单1中给出的文档更复杂的文档,您可以通过将内容处理程序推入和弹出堆栈来完成此操作。 通过以这种方式处理内容,您在每个处理程序方法中的代码都更少了。 减少这些方法的长度可以使它们更适合JIT编译器进行优化。
根据SAX解析器的配置,您的应用程序可能需要执行不同的操作。 例如,如果您依赖于字符串内部化,但是所使用的解析器不支持它,则您的应用程序必须使用equals()
比较字符串。 您可以有一个处理两种情况的内容处理程序,但这需要检查以查看每次解析器调用该处理程序时需要处理哪种情况。 您可以编写两个内容处理程序,而不是编写一个整体处理程序:一个对字符串执行引用比较,另一个不执行。 可以在解析之前确定要使用哪个处理程序。
使用实体解析器加载外部实体
引用外部DTD和/或包含许多对外部实体的引用的XML文档可能非常昂贵。 对于这些实体中的每一个,解析器都需要将资源定位在世界某处并读取它。 如果此资源位于您的硬盘驱动器上,则解析器必须打开一个文件。 如果解析器以单一编码在内部处理字符数据(例如Xerces2,该内部始终以16位单位(UTF-16)表示字符),则它必须对每个文件进行转码。 如果您的文档包含对网络或Internet上的实体的引用(并且可以从您的环境访问这些资源),则可能会导致较大的性能损失,尤其是在网络延迟较高的时期。 包括Xerces2在内的许多解析器都不会将已经读取的实体保留在内存中。 如果您的文档多次引用一个实体,则解析器将获取该实体的次数与被引用的次数相同。
如果您的XML文档引用了外部实体或外部DTD,则可以使用实体解析器将这些实体加载到内存中,从而提高应用程序的性能。 编写您的实体解析器,以便它在第一次读取实体时缓存实体的内容。 您的应用程序每个实体只需支付一次检索罚款。 如果不需要这种动态加载,则可以将希望从内存中读取的实体预加载到应用程序中。 当您将实体作为java.lang.String
存储在内存中时,可以避免解析器从实体的编码转换为字符时所产生的开销,如清单3所示。
清单3.从内存加载外部实体
public class MyEntityResolver implements EntityResolver {
private String externalEntity = ...;
InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
if (systemId.equals("ExternalEntity.xml") {
return new InputSource(new StringReader(externalEntity));
}
return null;
}
}
避免处理外部实体
尽管您的应用程序处理的XML文档可能包含对外部实体的引用,但是您可能对扩展它们不感兴趣。 SAX定义了两个功能,它们由功能URI http://xml.org/sax/features/external-general-entities
和http://xml.org/sax/features/external-parameters-entities
标识,用于控制是否解析器处理外部常规和外部参数实体。 如果禁用这些功能,并且在处理文档时遇到外部实体引用,则SAX解析器不会报告实体内容,而是会将实体名称报告给内容处理程序的skippedEntity
回调。 如果您的应用程序对外部实体的内容不感兴趣,则可以关闭这些功能以阻止对其进行处理。
通用DOM性能提示
DOM定义了几种类型的节点,例如Element
和Attribute
。 在编写代码以根据节点的类型执行特定操作时,请避免使用Java instanceof
运算符检查节点类型。 而是使用getNodeType
( Node
接口)方法来检索要处理的节点的类型。
在检索属性列表之前,请始终使用hasAttributes
方法进行查询以查看节点是否具有属性。 如果节点具有属性,则将该节点转换为Element
节点,并使用getAttributes
方法检索属性列表。 通过此操作序列,可以避免不必要地将Node
强制转换为Element
节点,并避免创建空的NamedNodeMap
-即使节点没有属性, getAttributes
方法也始终返回映射。
如果您的应用程序需要查询或修改属性Node
,请避免使用hasAttribute(String)
或hasAttributeNS(String, String)
方法。 而是使用getAttribute(String)
方法或getAttributeNS(String, String)
方法检索属性Node
,然后可以查询或修改该属性。
DOM API中的多个操作可能非常昂贵。 importNode
操作将导致创建新节点,因此您应考虑改用adoptNode
方法。 getElementByTagName
和getElementByTagNameNS
都使用Java String.equals()
方法比较名称和名称空间URI遍历DOM树以寻找节点。 取而代之的是,应用程序可以选择编写自己的遍历方法,这些方法可以使用Java ==
来比较字符串(以防字符串被内部化)或仅在树的一部分中进行搜索。 同样适用于getElementById
方法。
此外,请谨慎使用DOM 3级normalizeDocument
方法。 尽管此方法可以提高验证的性能,但它也可能很昂贵。 例如,默认情况下,此方法确保树的名称空间格式正确-也就是说,它检查是否已添加属性和元素的所有必需的名称空间声明,并且可以根据需要更改某些属性或元素的前缀。 如果该应用程序代码已经确定树的命名空间格式正确,则在调用normalizeDocument
方法之前,应用程序应使用DOMConfiguration
接口关闭“ namespace”参数。 这也适用于格式正确的参数,默认情况下为true。 请记住,通常DOM树的格式应正确。 如果开发人员在Comment
节点中包含--
或在CDATASection
节点中包含]]>
,或者在文本内容中使用非XML字符(包括CDATA部分和注释),则例外。 因此,对于大多数应用程序安全地调用normalizeDocument
时,可以禁用格式正确的参数,这会严重影响此方法的性能。
DOM 3级API
DOM 3级核心和加载与保存规范定义了几个新操作以及一个可以提高DOM应用程序性能的API。 因此,您应该计划迁移DOM应用程序以使用J2SE 5.0支持的DOM Level 3。
重命名和移动节点
在DOM级别2中,将节点重命名和从一个文档移动到另一个文档可能会相对昂贵,因为这些操作涉及创建新节点,复制这些节点的内容以及将节点插入树中的适当位置。 为了提高这些操作的性能,编写应用程序,以便它使用的renameNode
和adoptNode
方法。 通常, renameNode
方法仅更改节点的名称。 在极少数情况下,此方法可能最终会创建一个新节点,复制所有信息并将新节点插入树中。 当使用Xerces2 DOM时,只有在应用程序创建一个非命名空间感知节点(使用DOM级别1方法创建的节点,例如createElement
,然后再尝试使用命名空间URI重命名该节点时,才会发生这种情况。 由于Xerces2为支持名称空间的节点(使用DOM Level 2方法创建的节点,例如createElementNS
)和非命名空间节点使用不同的类,因此Xerces2 DOM实现被强制在renameNode
操作期间创建节点的新实例。 如上所述,这是一种罕见的情况,因为这种情况最经常在应用程序尝试在单个文档中混合名称空间感知节点和非名称空间感知节点时发生。 不建议将两种类型的节点混合在一起,因为这会导致不可预测的结果(例如,在树的验证期间)。
在内存中验证
使用DOM Level 3,您现在可以验证内存中的DOM。 过去,如果要根据模式进行验证,则必须编写自己的验证代码(这可能非常复杂),或者序列化DOM,然后使用验证解析器将其加载回内存中。 使用normalizeDocument
方法,可以在一个简单的步骤中执行DOM树的验证,以避免其他昂贵的操作。 在文章“ 发现DOM级别3核心的关键特性,第2部分 ”中阅读有关如何使用normalizeDocument
更多信息。
避免不必要的检查
通常,如果应用程序传递了错误的参数或执行了非法操作,则DOM实现必须验证操作的正确性并引发异常。 例如,对于createElementNS
方法,DOM实现必须验证qualifiedName
符合QName的定义(请参阅参考资料 )。 DOM 3级将新的strictErrorChecking
属性添加到Document
接口。 如果您确信应用程序对DOM执行的所有操作都是合法的(例如,如果DOM树是使用SAX事件构建的),则可以通过关闭严格的错误检查来提高性能。
使用加载和保存过滤器API
使用DOM Level 3,您不再需要等待解析完成就可以修改文档的结构。 新的筛选器API使您可以在解析期间通过要求解析器接受,跳过或从结果树中删除节点及其子元素来检查和修改文档的结构。 您也可以通过使用过滤器API中断解析来选择仅加载XML文档的一部分。 解析过程中对文档结构的此类修改可以导致DOM树的内存占用量较小,并且还可以减少遍历文档并在内存中进行修改所花费的时间。
使用串行器筛选器,您的应用程序可以指定要序列化为XML的节点,而无需修改原始DOM树。 这使您可以灵活地将同一DOM树序列化为多个XML文档,从而再次避免了可能昂贵的遍历和修改DOM树。
结论
在本文中,我们展示了如何提高XML应用程序的性能。 我们首先向您展示了编写XML以获得更好的解析性能的技术。 然后,我们描述了如何提高SAX和DOM应用程序的性能。 在本系列的第二篇文章中,我们将说明如果您使用Xerces2实现,则如何提高SAX和DOM应用程序的性能。 我们还将向您展示如何重用解析器实例。
翻译自: https://www.ibm.com/developerworks/xml/library/x-perfap1/index.html
xml应用程序下载