a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 181|回复: 3

[JAVA] 2011年计算机等考二级JAVA学习精华整理(79)

[复制链接]
发表于 2012-7-31 22:04:26 | 显示全部楼层 |阅读模式
  3.7 Java与XML联合编程之SAX篇7 x0 A/ w; `- O) v& M
  SAX概念
4 Q* c) r0 S: e2 u" I8 s+ H9 S; i' K  SAX是Simple API for XML的缩写,它并不是由W3C官方所提出的标准,可以说是“民间”的事实标准。实际上,它是一种社区性质的讨论产物。虽然如此,在XML中对SAX的应用丝毫不比DOM少,几乎所有的XML解析器都会支持它。
/ f( U! _) ]2 [% Y& B" e, k  与DOM比较而言,SAX是一种轻量型的方法。我们知道,在处理DOM的时候,我们需要读入整个的XML文档,然后在内存中创建DOM树,生成DOM树上的每个Node对象。当文档比较小的时候,这不会造成什么问题,但是一旦文档大起来,处理DOM就会变得相当费时费力。特别是其对于内存的需求,也将是成倍的增长,以至于在某些应用中使用DOM是一件很不划算的事(比如在applet中)。这时候,一个较好的替代解决方法就是SAX。
6 `5 m# s7 x/ I7 H, `  SAX在概念上与DOM完全不同。首先,不同于DOM的文档驱动,它是事件驱动的,也就是说,它并不需要读入整个文档,而文档的读入过程也就是SAX的解析过程。所谓事件驱动,是指一种基于回调(callback)机制的程序运行方法。(如果你对Java新的代理事件模型比较清楚的话,就会很容易理解这种机制了)
2 T9 g8 w. F8 N# _6 c  在XMLReader接受XML文档,在读入XML文档的过程中就进行解析,也就是说读入文档的过程和解析的过程是同时进行的,这和DOM区别很大。解析开始之前,需要向XMLReader注册一个ContentHandler,也就是相当于一个事件监听器,在ContentHandler中定义了很多方法,比如startDocument(),它定制了当在解析过程中,遇到文档开始时应该处理的事情。当XMLReader读到合适的内容,就会抛出相应的事件,并把这个事件的处理权代理给ContentHandler,调用其相应的方法进行响应。" u6 P! }& H& w9 E0 E% C, _
  这样泛泛的说来或许有些不容易理解,别急,后面的例子会让你明白SAX的解析过程。看看这个简单XML文件:
6 y% f* _4 x5 ]/ k  Ogden Nash7 ~0 L8 a) E' \
  Adam
$ n2 ^; A3 f" A, J' J/ U  当XMLReader读到标签时,就会调用ContentHandler.startElement()方法,并把标签名POEM作为参数传递过去。在你实现的startElement()方法中需要做相应的动作,以处理当出现时应该做的事情。各个事件随着解析的过程(也就是文档读入的过程)一个个顺序的被抛出,相应的方法也会被顺序的调用,最后,当解析完成,方法都被调用后,对文档的处理也就完成了。下面的这个表,列出了在解析上面的那个XML文件的时候,顺序被调用的方法:
, @  w/ g1 W9 V8 d- X* Q  遇到的项目 方法回调
, I) Q4 u; a) q, G  {文档开始} startDocument()& Z: X' O7 B$ \# B/ m
  startElement(null,"POEM",null,{Attributes})
- P# ~+ Z# C) C( V) v8 j2 L; T. [  "\n" characters("\n...", 6, 1)- J, D' p0 y1 g/ l/ x; B
  startElement(null,"AUTHOR",null,{Attributes})
, J0 a* [" }* V* Z  "Ogden Nash" characters("\n...", 15, 10)% C7 m( V8 L& O6 |7 d* g
  endElement(null,"AUTHOR",null)
0 e' O9 u6 a, {( {( _5 \  "\n" characters("\n...", 34, 1)
1 h1 n$ q# V9 z" Z( c  endElement(null,"TITLE",null)" l) d2 |) S6 w; @# z) u
  "\n" characters("\n...", 55, 1)5 z! i3 ]6 n5 \1 g! W7 K
  startElement(null,"LINE",null,{Attributes}). W3 c. a- t/ g' D
  "Adam" characters("\n...", 62, 4)3 c' M% `- Y8 {- l% t: ^
  endElement(null,"LINE",null)- I" Z7 c% R- C( s
  "\n" characters("\n...", 67, 1)
) C; a. T" t5 v2 [" X; K  i  endElement(null,"POEM",null)
1 k( f+ h$ |+ @9 f2 K  {文档结束} endDocument()
回复

使用道具 举报

 楼主| 发表于 2012-7-31 22:04:27 | 显示全部楼层

2011年计算机等考二级JAVA学习精华整理(79)

 ContentHandler实际上是一个接口,当处理特定的XML文件的时候,就需要为其创建一个实现了ContentHandler的类来处理特定的事件,可以说,这个实际上就是SAX处理XML文件的核心。下面我们来看看定义在其中的一些方法:  void characters(char[] ch, int start, int length):4 H, n9 z" b. y( @4 i) S
  这个方法用来处理在XML文件中读到字符串,它的参数是一个字符数组,以及读到的这个字符串在这个数组中的起始位置和长度,我们可以很容易的用String类的一个构造方法来获得这个字符串的String类:String charEncontered=new String(ch,start,length)。
+ `7 A8 {  K. ~* y  void startDocument():' }& _, A. ]; p) z6 f
  当遇到文档的开头的时候,调用这个方法,可以在其中做一些预处理的工作。0 u1 f5 b8 {* l, l7 i( a% x  \8 W
  void endDocument():/ b# C" T! c9 _  w( z8 h, n3 l
  和上面的方法相对应,当文档结束的时候,调用这个方法,可以在其中做一些善后的工作。+ Y* S3 C' M) A  I% y
  void startElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName, Attributes atts)7 B3 I" j( b+ ?" z6 E+ c( R
  当读到一个开始标签的时候,会触发这个方法。在SAX1.0版本中并不支持名域,而在新的2.0版本中提供了对名域的支持,这儿参数中的namespaceURI就是名域,localName是标签名,qName是标签的修饰前缀,当没有使用名域的时候,这两个参数都未null。而atts是这个标签所包含的属性列表。通过atts,可以得到所有的属性名和相应的值。要注意的是SAX中一个重要的特点就是它的流式处理,在遇到一个标签的时候,它并不会纪录下以前所碰到的标签,也就是说,在startElement()方法中,所有你所知道的信息,就是标签的名字和属性,至于标签的嵌套结构,上层标签的名字,是否有子元属等等其它与结构相关的信息,都是不得而知的,都需要你的程序来完成。这使得SAX在编程处理上没有DOM来得那么方便。; X# D1 d0 a% E8 }; e
  void endElement(java.lang.String namespaceURI, java.lang.String localName, java.lang.String qName)6 T9 F% ?: T, @9 h: l% g5 g% ?) z# \
  这个方法和上面的方法相对应,在遇到结束标签的时候,调用这个方法。. E# j& q3 R7 L$ u7 u
  因为ContentHandler是一个接口,在使用的时候可能会有些不方便,因而,SAX中还为其制定了一个Helper类:DefaultHandler,它实现了这个接口,但是其所有的方法体都为空,在实现的时候,你只需要继承这个类,然后重载相应的方法即可。
$ }9 s  L& @' {, n8 ?6 Z9 t* O, E  到这儿SAX的基本知识已经差不多讲完了,下面我们来看看两个具体的例子,以更好的理解SAX地用法。: D% f$ l# W$ e: c; N, M* @
  SAX编程实例5 S+ o! Q0 x: ]. O- T
  我们还是沿用讲DOM的时候使用的那个文档例子,但首先,我们先看一个简单一些的应用,我们希望能够统计一下XML文件中各个标签出现的次数。这个例子很简单,但是足以阐述SAX编程的基本思路了。
- s# s9 q# @( \/ {  一开始当然还是import语句了:9 T! h, s  i, T
  import org.xml.sax.helpers.DefaultHandler;- q1 H( t$ M& z& B( E8 @% }; Z6 [
  import javax.xml.parsers.*;1 T  z6 s) L; K
  import org.xml.sax.*;
( m5 i: d) B  A) Q  import org.xml.sax.helpers.*;; {$ U0 l  q* Z# h/ q8 L
  import java.util.*;- V4 J: k3 }) k  p4 P
  import java.io.*;& E* ~( n( Q% o; a
  然后,我们创建一个继承于DefaultHandler的类,具体的程序逻辑在这儿可以暂且放在一边,要注意的是程序的结构:
5 s- N3 ?/ S- P/ R1 X4 v  public class SAXCounter extends DefaultHandler {
- h9 d  f7 F4 g" }6 w5 j  private Hashtable tags; //这个Hashtable用来记录tag出现的次数
$ @; g% a, M$ ^, l  // 处理文档前的工作) F; L) G) }5 g+ \
  public void startDocument() throws SAXException {
4 Q  V4 {+ q: a) \  tags = new Hashtable();//初始化Hashtable
7 v- s1 u! w0 Q8 c' j+ j/ C  }
$ o7 C; W6 ]' }! e# x6 D  //对每一个开始元属进行处理
! e2 Q/ g2 {+ M" {" ^- |  public void startElement(String namespaceURI, String localName,; J1 M! _" X" w9 q" |' A
  String rawName, Attributes atts), {) n5 i& \* |
  throws SAXException9 X, G# R' b6 ^" c8 Y
  {
  X; z8 ?4 l; q/ C' i  String key = localName;
1 C0 h- l$ W8 v6 l3 z" B  Object value = tags.get(key);" {/ n7 u. R8 [4 ~8 E" ^
  if (value == null) {! `) t+ p0 O- B" O4 {
  // 如果是新碰到的标签,这在Hastable中添加一条记录
$ i& Z( N" G' D# }# v" c  tags.put(key, new Integer(1));
) v# s0 w; v- S. z4 s! |5 \  } else {
$ C% v9 [2 {( d/ V3 ~4 a. i8 V  // 如果以前碰到过,得到其计数值,并加1
( C; Q) V+ q- c8 A9 `6 w; [  int count = ((Integer)value).intValue();) s4 E4 s$ g; t& t1 }% G
  count++;2 ?+ H  |( o- ~2 ^/ `2 K1 |7 q
  tags.put(key, new Integer(count));1 X# m& p) v& f. K0 P$ L
  }
  g8 x: P4 \  `$ D3 o, F8 i, n  }4 e0 k- A4 L# b: b
  //解析完成后的统计工作
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-7-31 22:04:28 | 显示全部楼层

2011年计算机等考二级JAVA学习精华整理(79)

 public void endDocument() throws SAXException {  Enumeration e = tags.keys();# w& {8 M. u/ @' ~: G' F" r( [
  while (e.hasMoreElements()) {9 l$ U! p' v4 @: P' C
  String tag = (String)e.nextElement();3 @, U& W; F6 P! k7 f
  int count = ((Integer)tags.get(tag)).intValue();: ^) p  O# x: h* h" l+ a
  System.out.println("Tag  occurs " + count0 j" d5 ~3 E# D6 {
  + " times");( u. Q& i7 t( }! S% q
  }8 [/ k5 v; c1 {( ~
  }
9 c( S1 P4 F% k  }; ^  //程序入口,用来完成解析工作
# t9 l( Z. N& {, X6 j7 k0 @- S/ Z  static public void main(String[] args) {4 m. @+ V3 j: r0 z4 d
  String filename = null;% U  P6 O3 d) k. M( Q( ?" _
  boolean validation = false;2 s* \1 h* f! e
  filename="links.xml";
8 B  M9 v5 T5 ]# k1 d  SAXParserFactory spf = SAXParserFactory.newInstance();
, n& l$ ^- _3 B0 N  XMLReader xmlReader = null;3 ]/ L- [6 e/ w* V6 v2 O+ H
  SAXParser saxParser=null;' C4 Z4 W% {7 T/ P- W" S
  try {
0 }! U3 p, w$ V9 l# s. V  // 创建一个解析器SAXParser对象
  y! c- x2 Y' P" q& Q4 k  saxParser = spf.newSAXParser();
/ ^5 m8 o: r6 Q9 h1 N2 g8 u0 G( [1 B  // 得到SAXParser中封装的SAX XMLReader
3 y2 \8 |, i1 F# x7 c1 W. |  ]. U! D  xmlReader = saxParser.getXMLReader();
# d2 X% H& k, l  } catch (Exception ex) {
6 I7 r/ z5 `- H4 o' C) c% [  System.err.println(ex);9 i9 y9 \; V6 h
  System.exit(1);
- I5 A. J6 t3 p' X; H- u/ g4 y  }. o0 Z# O* D3 F" f1 H& j% E+ ^2 S
  try {
" K1 W; S( E# a" B5 c  //使用指定的ContentHandler,解析给XML文件,这儿要注意的是,为了1 W( l0 z2 J. p, @$ \0 F
  //程序的简单起见,这儿将主程序和ContentHandler放在了一起。实际上5 j/ k5 k' [# Z
  //main方法中所作的所有事情,都与ContentHandler无关。
( B6 E5 ]6 K9 X3 {! G  xmlReader.parse(new File(filename),new SAXCounter());- A9 y$ O4 L( k9 V* a# d
  } catch (SAXException se) {1 d1 r, ^/ N2 c* j" J7 n& U9 m# i9 M
  System.err.println(se.getMessage());
& B9 g5 F) @: c; g  m  System.exit(1);4 Z( r3 W  Q  Z$ G; x) [
  } catch (IOException ioe) {
% `7 V' x- k, g  System.err.println(ioe);
2 Y# Q# Y+ g% \" y2 e/ C8 v3 l5 o  System.exit(1);+ H& o( K  e* }/ G' Y2 G. r: k
  }$ K7 b; |3 |/ |# ?( i+ D
  }
5 Q/ _9 r* S$ |  }
7 t( {: x4 S. b2 M( Q  我们来看看这段程序作了些什么,在main()方法中,主要做的就是创建解析器,然后解析文档。实际上,在这儿创建SAXParser对象的时候,为了使程序代码于具体的解析器无关,使用了同DOM中一样的设计技巧:通过一个SAXParserFactory类来创建具体的SAXParser对象,这样,当需要使用不同的解析器的时候,要改变的,只是一个环境变量的值,而程序的代码可以保持不变。这就是FactoryMethod模式的思想。在这儿不再具体讲了,如果还有不明白的,可以参看上面DOM中的解释,原理是一样的。' E! C+ F( ?: ^, K2 P8 n' X
  不过在这儿还有一点点要注意的地方,就是SAXParser类和XMLReader类之间的关系。你可能有些迷糊了吧,实际上SAXParser是JAXP中对XMLReader的一个封装类,而XMLReader是定义在SAX2.0种的一个用来解析文档的接口。你可以同样的调用SAXParser或者XMLReader中的parser()方法来解析文档,效果是完全一样的。不过在SAXParser中的parser()方法接受更多的参数,可以对不同的XML文档数据源进行解析,因而使用起来要比XMLReader要方便一些。) f5 j6 G1 c- E: K3 h& }
  这个例子仅仅涉及了SAX的一点皮毛,而下面的这个,可就要高级一些了。下面我们要实现的功能,在DOM的例子中已经有实现了,就是从XML文档中读出内容并格式化输出,虽然程序逻辑看起来还是很简单,但是SAX可不比DOM哦,看着吧。
3 j; x" f3 K9 n0 H3 v* ?  前面说过,当遇到一个开始标签的时候,在startElement()方法中,我们并不能够得到这个标签在XML文档中所处的位置。这在处理XML文档的时候是个大麻烦,因为在XML中标签的语义,有一部分是由其所处的位置所决定的。而且在一些需要验证文档结构的程序中,这更是一个问题。当然,没有解决不了的问题了,我们可以使用一个栈来实现对文档结构的纪录。
8 F% o* i0 ?+ p% S% n2 C  栈的特点是先进先出,我们现在的想法是,在startElemnt()方法中用push将这个标签的名字添加到栈中,在endElement()方法中在把它pop出来。我们知道对一个结构良好的XML而言,其嵌套结构是完备的,每一个开始标签总会对应一个结束标签,而且不会出现标签嵌套之间的错位。因而,每一次startElement()方法的调用,必然会对应一个endElement()方法的调用,这样push和pop也是成对出现的,我们只需要分析栈的结构,就可以很容易的知道当前标签所处在文档结构中的位置了。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-7-31 22:04:29 | 显示全部楼层

2011年计算机等考二级JAVA学习精华整理(79)

 public class SAXReader extends DefaultHandler {  java.util.Stack tags=new java.util.Stack();3 e  `5 ]8 q6 E/ W4 n5 H: X9 t
  //--------------XML Content-------------, F* D# S2 m3 ~5 l4 [2 m
  String text=null;
$ Z& g  `- u. D/ a7 C% g  String url=null;
- R' f0 F- O% e% o% r, n! M  String author=null;
' ^  b  ~/ h/ O" m9 Y9 E  String description=null;
2 a! X: X3 p, I) C, V4 q/ \  String day=null;
. o; i8 n6 {& T. c7 t/ S  String year=null;
% X- M! U3 _( c, h+ o3 G  String month=null;
/ g# R: R( _7 y* f0 c# p  //----------------------------------------------
) S- N! O& H1 ~% R* {4 G/ b/ E1 r  public void endDocument() throws SAXException {
* ~% }, b* ?- G  System.out.println("------Parse End--------");
: U/ e' @4 k: h* n  D/ i# W3 E  }
4 e8 ^% K( X9 |* C5 R/ O% y( m3 |. }  public void startDocument() throws SAXException {
" Y% k. f& Z" G  [. \  System.out.println("------Parse Begin--------");( d. A$ A% `0 o
  }
+ ~) J* C+ j! J9 ^  public void startElement(String p0, String p1, String p2, Attributes p3) throws SAXException {+ [0 G1 s+ H* B1 G
  tags.push(p1);
8 M( j  g  F; t' h0 I  }
2 U* A+ z/ R9 ?- j  V  public void endElement(String p0, String p1, String p2) throws SAXException {
, o4 n; M- `# h& g0 d8 l- A  tags.pop();5 ~3 P5 J6 E+ k$ z  a, R3 k0 H
  //一个link节点的信息收集齐了,将其格式化输出8 t+ g8 J. F7 i$ W8 K
  if (p1.equals("link")) printout();2 q& N) d4 a6 J- a
  }) K; M4 S6 O8 o, F" J0 d! M
  public void characters(char[] p0, int p1, int p2) throws SAXException {
+ L: r& f% r' s5 b" c  //从栈中得到当前节点的信息
3 o- i3 n+ L1 Z+ b. W6 O6 Z& @  String tag=(String) tags.peek();
! G1 w9 F# {8 t5 ?" v, ~' g$ M  if (tag.equals("text")) text=new String(p0,p1,p2);, f- v4 n$ v5 ?. r) A
  else if (tag.equals("url")) url=new String(p0,p1,p2);
* q( X. O+ f+ s! E+ p- r  else if (tag.equals("author")) author=new String(p0,p1,p2);
: k) H4 l6 G& V  else if (tag.equals("day")) day=new String(p0,p1,p2);5 g6 r1 s1 q: F/ B4 A
  else if (tag.equals("month")) month=new String(p0,p1,p2);
! |+ O+ m. k4 {! z9 Q. b  else if (tag.equals("year")) year=new String(p0,p1,p2);
7 x( s5 k4 i: z' Z2 i: k  else if (tag.equals("description")) year=new String(p0,p1,p2);
" R2 X% G' U5 B0 o! i- O) x  }+ F/ ^. T! n4 Q$ E: o: w
  private void printout(){$ o; `4 i5 r+ z; ]# m5 [& V( j
  System.out.print("Content: ");4 W" T3 W; k' T% G4 T+ T) D
  System.out.println(text);
0 H0 n$ X0 F! F  System.out.print("URL: ");
" Y5 J0 |) P- N; k: n: Y+ k  System.out.println(url);4 O) \) W# T4 r* O) m4 O
  System.out.print("Author: ");
8 G7 L8 G- U. z! f  System.out.println(author);
& _2 U( B1 B, G% U! |/ x  System.out.print("Date: ");- e" e5 K+ t  e. `+ i! v
  System.out.println(day+"-"+month+"-"+year);( ?9 l& S+ L5 P9 R
  System.out.print("Description: ");
/ d; c, {" ^& x  System.out.println(description);
) B; z. I! q% }$ u; r  o1 a  System.out.println();" x! e% S8 O$ b" E1 x1 k- L
  }
7 `; D7 E- t3 W8 ~3 K5 o9 r  static public void main(String[] args) {
! X4 y6 F. a- |6 V( S  String filename = null;
3 w# j9 X# ]2 J  boolean validation = false;
3 P* _+ U* d; `/ g6 A. q  filename="links.xml";
" a7 L, h* f  D- ]" B  SAXParserFactory spf = SAXParserFactory.newInstance();3 ]) r$ C8 s8 v
  SAXParser saxParser=null;
' a0 u  o, T* ^- T+ c2 S+ \1 P7 T# G  try {% C2 i0 ]. [% b; J9 B/ t& E2 |
  saxParser = spf.newSAXParser();; _- \9 n' C5 T/ U1 T
  } catch (Exception ex) {
% x# F) ^+ X. v' b3 Y3 W) K- _  System.err.println(ex);
6 y0 R: T  H, o  D, w1 A  System.exit(1);( w+ f1 q' N6 H) \
  }2 N; F. o' B% H
  try {
5 N) I+ J8 c0 y. H4 c/ B1 R1 n2 y7 [  saxParser.parse(new File(filename),new SAXReader());' ^+ H5 n7 ?- j4 ^4 p; f6 w* W" |" d
  } catch (SAXException se) {( E8 G( q4 W1 L- b4 N9 D0 q  H
  System.err.println(se.getMessage());
* w# j' f) S  G# x$ g( ^  System.exit(1);
; D/ M: p. a! O" g, y2 X  } catch (IOException ioe) {
* ]( m* `. a4 V# Z3 T5 _9 G' _  System.err.println(ioe);$ s6 B% u; `: q( Z0 `
  System.exit(1);/ M" Q& B" R+ c! `  e& M
  }
0 g, ?1 k4 N# M" Y, h9 ]% D  O" y  }2 O8 n2 Z, v7 d" @6 g/ Q
  }, k+ ~* K2 `  x9 |' Z9 q7 K: @
  在这儿虽然没有使用到栈的分析,但实际上栈的分析是一件很容易的事情,应为java.util.Stack继承了java.util.Vector类,而且Stack中的元素是按栈的结构由底至上排列的,因个,我们可以使用Vector类的size()方法来得到Stack的元素个数,还可以使用Vector的get(int)方法来得到具体的每一个元属。实际上,如果把Stack的元素从底向上逐一排列出来,我们就得到了从XML根节点到当前节点的一条唯一的路径,有了这条路径的信息,文档的结构就在清楚不过了。5 F0 P* t: P4 T/ Y7 t2 r7 M6 E; N- J
  小节
$ [3 m3 s1 ~2 t9 P" ^' Q5 P  好了,到这儿为止,我们已经掌握了对于XML编程的两大利器:DOM和SAX,也知道了该如何在一个Java程序中使用它们。DOM编程相对简单,但是速度比较慢,占用内存多,而SAX编程复杂一些,但是速度快,占用内存少。所以,我们应该根据不同的环境选择使用不同的方法。大部分的XML应用基本都可以用它们来解决。需要特别说明的是,DOM和SAX其实都是语言无关的,并非Java所独有,也就是说,只要有相应的语言实现,DOM和SAX可以应用在任何面向对象的语言中。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-5-2 23:01 , Processed in 0.178559 second(s), 27 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表