SAX 实现“增量文件”
(wang hailong)
本文假设您对XML,DOM,SAX等“词儿”比较熟悉。J
本文所举的例子都用Java语言,但原理相通,同样适用于C++和C#。
1.XML格式的“增量文件”
我们知道,编程打开文件的方式有几种,read,write,append,等等。Append方式打开的文件就可以称为“增量”文件,新的内容可以被增加到文件的末尾。Log日志文件的纪录,基本都是采取这种方式。“增量文件”的一种重要应用就是Log日志文件。我们不用考虑文件里面“已有”的内容,只要简单地添加,添加。
对于复杂的情况,Append方式就不够用了。如果我们在Log日志文件用到了格式处理,(如HTML格式,XML格式),这时,就不能把新的内容简单地添加到文件的末尾,我们必须考虑文件里面“已有”的内容,把新的内容放在合适的位置。比如,Log日志文件采用HTML格式,我们至少需要把新的内容放在放在<html><body>和</body></html>标签的中间,甚至应该放在<table>和</table>等标签的中间。我们要保证文件格式的正确,信息位置的正确。
本文提到的格式信息,结构信息,特指XML格式。文中讨论的“增量文件” 特指XML格式的“增量文件”。本文旨在说明解析、操作XML格式“增量文件”的方法。
2.XML数据的处理方法
2.1 XML Database
如果你需要对文件数据进行功能完善的管理,添加,删除,查询,比较等等,请考虑使用XML Database,请参考一些Open Source Project,比如Apache XIndice。
2.2 DOM + XPath
解析文件,生成DOM Tree,对DOM Tree的节点进行添加操作。可以和XPath进行配合使用,比如,想一步定位到目标节点,”HTML/BODY//TABLE[position()=last()]”,定位到HTML文件的最后一个table,把需要添加的栏目加到这个table中。
关于XPath的使用,可以参考Apache Xalan的例子,关于XPathAPI的例子。
优点:概念清晰,结构良好,代码易读。
缺点:占用较多的时间和空间,使用XPath反复搜索DOM Tree的情况尤其如此。
2.3 SAX + Filter<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
解析文件,产生SAX事件,处理SAX事件。SAX以Pipeline管道方式运作,可以在Pipeline管道上加入一些Filter,进行链式处理,前一步SAX事件的处理结果,成为下一步的SAX事件。
缺点:SAX的概念不如DOM直观,代码的易读性不如DOM,结果只能一遍生成,不能反复搜索。SAX编程的结构性通常不如DOM。
优点:SAX的编程难度并不比DOM高,需要的编码量甚至常常少于DOM的编码量。SAX快速,占用空间很少。
2.4 XML-Object Binding
XML数据和Java对象进行绑定。把XML元素映射为一个Java对象,XML元素的属性或子元素映射为Java对象的成员变量。
相关的Open Source Project : Sun Jaxb; Castor. 等等。
绑定方式分为静态绑定和动态绑定。动态绑定比较灵活,但速度比较慢。本文倾向于静态绑定,后面主要讲静态绑定的特性。
静态绑定的过程:编写XML Schema文件;用代码生成工具处理XML Schema文件,自动生成java类;使用自动生成的java类的对象,相当于直接操作XML数据。
优点:这种方法的编程难度最小,代码的结构化最好。速度比DOM快(快的有限),空间比DOM小(小的有限),和DOM一样,生成结果树,能够反复搜索查找。
缺点:速度比SAX慢,占空间比SAX大。当文档结构复杂到一定程度,自动生成的代码成为一个负担。
2.5 关于Apache Project的其它参考
http://xml.apache.org 核心为 Apache Xerces和Apache Xalan.
http://jakarta.apache.org/commons/digester.html (small size) Digester : XML-Object动态绑定。
http://jakarta.apache.org/commons/jxpath/index.html JXPath : 使用XPath操作Java层级对象。
3.SAX实现“增量文件”
现在切入正题。SAX的时间和空间效率最高,本文主要讨论这种方法。
SAX的核心接口是ContentHandler接口,ContentHandler接受SAX事件,进行处理。
我们现在面对的问题是文件操作,首先需要的是一个能够把SAX事件写到文件里面的ContentHandler。这个类对于我们的问题非常重要。
Apache Xerces和Apache Xalan提供了一些XML的Serializer类,提供了一些XML的Filter类,还实现了transform包里面的Source和Result接口。合理地把这些类组装成起来,就可以实现一个灵活的处理管道。
因为本文对应的问题很小,不必要引入这样的复杂度。ContentHandler接口并不是很复杂,而且有这么多的开放源代码可以参考,我们自己来实现一个最简单的SAXWriter类,用来把SAX事件写到一个文件里面。下面的代码是从Apache Xerces的代码中抽取出来,实现最简化的功能。
3.1 SAXWriter的代码
package example;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.DefaultHandler;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.io.OutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class SAXWriter extends DefaultHandler{
public SAXWriter() {
}
/** Print writer. */
protected PrintWriter printWriter;
/** Sets the output stream for printing. */
public void setOutput(OutputStream stream, String encoding)
throws UnsupportedEncodingException {
if (encoding == null) {
encoding = "UTF8";
}
java.io.Writer writer = new OutputStreamWriter(stream, encoding);
printWriter = new PrintWriter(writer);
} // setOutput(OutputStream,String)
/** Sets the output writer. */
public void setOutput(java.io.Writer writer) {
printWriter = writer instanceof PrintWriter
? (PrintWriter) writer : new PrintWriter(writer);
} // setOutput(java.io.Writer)
// ContentHandler methods
/** Start element. */
public void startElement(String uri, String local, String raw,
Attributes attrs) throws SAXException {
printWriter.print('<');
printWriter.print(raw);
if (attrs != null) {
int len = attrs.getLength();
for (int i = 0; i < len; i++) {
printWriter.print(' ');
printWriter.print(attrs.getQName(i));
printWriter.print("=/"");
printWriter.print(attrs.getValue(i));
printWriter.print('"');
}
}
printWriter.print('>');
printWriter.flush();
} // startElement(String,String,String,Attributes)
/** End element. */
public void endElement(String uri, String local, String raw) throws
SAXException {
printWriter.print("</");
printWriter.print(raw);
printWriter.print('>');
printWriter.flush();
} // endElement(String)
/** Characters. */
public void characters(char ch[], int start, int length) throws SAXException {
printWriter.write(ch, start, length);
printWriter.flush();
} // characters(char[],int,int);
/** Ignorable whitespace. */
public void ignorableWhitespace(char ch[], int start, int length) throws
SAXException {
characters(ch, start, length);
printWriter.flush();
} // ignorableWhitespace(char[],int,int);
}
下面扩展这个类。
3.2 ContentAppender的源代码
package example;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.helpers.DefaultHandler;
public class ContentAppender extends SAXWriter{
String root = null;
String message = null;
public ContentAppender(){
}
public void appendMessage(String message){
this.message = message;
}
/** Start element. */
public void startElement(String uri, String local, String raw, Attributes attrs)
throws SAXException {
if(root == null){ // record the root element
root = raw;
}
super.startElement(uri, local, raw, attrs);
}
/** End element. */
public void endElement(String uri, String local, String raw) throws SAXException {
if(message != null && raw.equals(root)){ // if it is the end of root element
super.startElement("", "message", "message", null);
printWriter.print(message);
super.endElement("", "message", "message");
printWriter.println();
}
super.endElement(uri, local, raw);
}
}
扩展SAXWriter,我们可以把新添加的内容放置在任何一个元素里面。ContentAppender类把新添加的内容加在根元素的最后一个节点。
3.3 主函数
package example;
// java
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
// org.xml
// Imported SAX classes
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.ParserAdapter;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.helpers.XMLFilterImpl;
import org.xml.sax.XMLReader;
import org.xml.sax.XMLFilter;
import org.xml.sax.ContentHandler;
// Imported Serializer classes
import org.apache.xalan.serialize.Serializer;
import org.apache.xalan.serialize.SerializerFactory;
import org.apache.xalan.templates.OutputProperties;
public class IncrementalWriter {
public void process(String logFile, String newMessage) throws Exception{
String newFileName = logFile + ".new";
// Create an XMLReader.
XMLReader reader =
XMLReaderFactory.createXMLReader ("org.apache.xerces.parsers.SAXParser") ;
ContentAppender handler = new ContentAppender();
FileOutputStream fo = new FileOutputStream(newFileName);
handler.setOutput(fo, null);
handler.appendMessage(newMessage);
reader.setContentHandler(handler);
reader.parse(logFile);
fo.close();
// delete old file and rename new file to log file.
File oldFile = new File(logFile);
oldFile.delete();
File newFile = new File(newFileName);
newFile.renameTo(new File(logFile));
}
// for example, a.xml is as below
// <root>
// <message>old message</message>
// <root>
public static void main(String[] args) throws Exception{
IncrementalWriter tester = new IncrementalWriter();
tester.process("a.xml", "hello"); // append “hello” to a.xml file.
}
}
这三个文件很简单。Jdk1.4下编译通过。执行的结果是,在a.xml文件中,增加一条元素。
4.应用建议
以上讲述了一个最简单的例子,(限于能力,我无法再简化了)。
以上的代码,每写一条信息,就要处理一次文件,这样频繁读写文件的效率很低。
实际的应用中,可以结合其它方法。比如,先把信息存在内存的DOM Tree或者Java Object里面,存放到一定数量的时候,再调用类似上面的代码,把数据一次存放到文件中。
本文推荐一种方法,把XML-Object Binding和SAX结合使用。比如,使用jaxb的XML-Object binding机制,用XML-Object存放结构简单的部分数据,然后用SAX把这些数据一次一次地存放到结构复杂的XML文件里面的某个指定位置。
当文件变得很大的时候,建议分成几个文件存储。
有可能加快内存回收的代码如下(可以照此释放DOM Tree或者Java Object):
Vector v = new Vector(2000);
// process vector, after that
v = null; // cancel the reference
System.gc(); // suggest JVM to collect unused Object
5.按照XML格式解析HTML
HTML的结构性不够好,所以出现了结构化文档XML。事物总是相互影响的。HTML的不足催生了XML,XML的出现和发展,又反过来影响HTML的处理过程。
下面简单介绍按照XML结构解析HTML页面的方法——用DOM或者SAX解析HTML。
5.1 HTMLBuider
Apache Xerces包含一个org.apache.html.dom包,实现了这些HTML文档元素的接口。其中的HTMLBuider类是入口类,实现了org.xml.sax.DocumentHandler接口,接受SAX事件,生成HTML文档树。
这种方法的关键是找到一个合适的XMLReader,对HTML文档进行解析。很多XML解析器的容忍度很低,大多数HTML文档不是良好结构的XML,所以,经常有一些重要的HTML元素解析不出来。
5.2 NekoHTML Project
NekoHTML Open Source Project使用Apache Xerces的XNI(Xerces Native Interface)接口,对HTML文档进行解析,是一个不错的开源项目。
能够基本上不遗漏地处理每一个HTML元素生成HTML文档树。能够基本上不遗漏地对应每一个HTML元素产生SAX事件。
NekoHTML可以采用前述的两种方法处理HTML文档——(1)DOM+XPath,(2)SAX+Filter。
DOM+XPath的例子很直观,简单易读,容易上手。SAX+Filter比较难以理解,不易上手。
使用SAX+Filter的步骤:
1.建立自己的Filter,实现org.apache.xerces.xni.parser.XMLDocumentFilter接口。
参见org.cyberneko.html.filters包。
2.创建org.cyberneko.html.parsers.SAXParser。调用
parser.setFeature("http://cyberneko.org/html/features/balance-tags", false); // optional
XMLDocumentFilter[] filters = new XMLDocumentFilter[]{myFilter, writer}; // create filters
parser.setProperty("http://cyberneko.org/html/properties/filters", filters); // set filters
parser.parse(…);
6.总结
XML和相应工具的出现,大大减轻了我们的文档处理的工作量,加快了我们的工程项目的进度。J
相关推荐
在IT行业中,XML...在提供的"testDomXml"文件中,可能包含了实现上述功能的示例代码或测试数据。通过分析和学习这个文件,开发者可以更好地理解如何在实际项目中应用这些技术来处理大型XML文件并展示其树状结构。
例如,可能会有一个示例展示如何使用DOM解析XML并提取特定元素,另一个示例可能展示了如何使用SAX进行增量解析,避免加载整个文档。这些示例可以帮助开发者深入理解XML处理的细节,并在实际项目中应用。 通过研究...
描述中的“边写边验证”功能强调了在数据写入XML文件的同时即可进行验证,这相当于一种增量验证机制。它的优势在于可以即时发现并纠正错误,而不需要等到写入过程结束,从而节省了在错误发现后重新处理整个文件的...
这涉及到读取文件内容,然后使用解析库(如Java的DOM或SAX,JavaScript的JSON.parse,Swift的JSONSerialization)将数据转换为可操作的对象。 4. 动态应用皮肤: 一旦解析了皮肤文件,我们需要将其中的样式信息...
- **MainFrm.cpp、ChildFrm.cpp**:这些文件可能包含了应用程序的主框架窗口和子框架窗口的实现,是MFC应用程序的标准部分。 - **StdAfx.cpp**:这个文件包含了预编译头,通常包含常用的库头文件,以减少编译时间。 ...
- **易用性差**:SAX需要开发者手动管理状态,实现复杂逻辑时难度较大。 #### VTD-XML的提出及其特点 针对DOM和SAX存在的问题,VTD-XML提供了一种新的非提取式(non-extractive)XML解析方法,有效地解决了传统解析...
2. **SAX:** 是事件驱动的解析器,逐行读取XML文档,适用于大型文档,因为它是增量解析,不需要全部加载。 3. **StAX:** 结合了DOM和SAX的优点,允许开发者以流式方式读写XML,对内存要求较低。 **asm-2.2.1.jar....
通常,我们会选择效率高且内存消耗小的`SAX`或`Pull Parser`来解析XML文件中的版本信息。 3. **版本信息比较**:在XML中,应包含当前服务器端的最新版本号。解析XML时,提取这个版本号并与本地应用的版本号进行比较...
3. **事件驱动解析(SAX)与模型驱动解析(DOM)**:DOM4J2既支持SAX(Simple API for XML)的增量解析,也支持DOM的整个文档加载。SAX适用于处理大文件,而DOM适合于小文件或内存受限的情况。 4. **文档构建和修改...
在这个“xml.rar_android”压缩包中,包含了与XML相关的C代码实现以及在Android平台上处理XML的基本操作。下面我们将深入探讨XML的基础知识以及在Linux下和Android平台上的应用。 首先,让我们了解XML的基本概念。...
- 可能还包括了错误处理和性能优化的技巧,例如使用DOM的SAX解析器进行增量解析以减少内存消耗。 通过学习和实践这些XML编程技巧,开发者能够更有效地处理XML数据,提升程序的灵活性和可维护性。在实际项目中,...
- 使用 SAX 进行增量式解析。 **3.8 Java与XML联合编程之DOM篇** - **DOM解析** - 使用 DOM 对整个文档进行操作。 #### 四、其他 **4.1 代码复用的规则** - **代码复用** - 如何有效地重用代码。 **4.2 ...
- **SAX解析器**:适用于处理大型XML文件,因为它按事件流处理,不会一次性加载整个文档到内存,节省资源。 - **Pull解析器**:适用于迭代读取XML数据,如XmlPullParser,它是基于事件驱动的,适合进行增量解析。 ...
7. **性能考虑**:由于XML文件可能很大,libxmldiff需要有效处理大文件,可能采用增量比较或内存管理策略来优化性能。 8. **兼容性**:库可能支持多种操作系统和编程语言,例如跨平台的C/C++编译器,或者提供其他...
动态INCLUDE用jsp:include动作实现 它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数。 静态INCLUDE用include伪码实现,定不会检查所含文件的变化,适用于包含静态页面...
动态INCLUDE用jsp:include动作实现 它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数。 静态INCLUDE用include伪码实现,定不会检查所含文件的变化,适用于包含静态页面...
可能采用增量解析或SAX(Simple API for XML)方式减少内存消耗。 - 缓存策略可以提高重复访问的效率。 8. **安全性**: - XML解析器需要防范诸如XXE(XML External Entity攻击)和XSS(跨站脚本攻击)等安全风险...
- **配置文件**:使用DOM或SAX解析XML配置文件,如数据库连接配置、应用配置等。 - **数据交换**:将业务数据序列化为XML格式进行传输,使用DOM或SAX进行解析。 - **元数据管理**:使用XML描述元数据,便于数据管理...
插入/更新步骤用于将数据插入或更新到数据库表中,支持多种更新策略,如唯一键更新、全部字段更新、增量更新等,可以用于处理数据同步或批量导入。 **9.6.13 更新(Update)** 更新步骤用于更新数据库表中的现有...