SAX的全称是Simple APIs for XML,也即XML简单应用程序接口。
与DOM不同,SAX提供的访问模式是一种顺序模式,这是一种快速读写XML数据的方式。
当使用SAX分析器对XML文档进行分析时,会触发一系列事件,并激活相应的事件处理函数,应用程序通过这些事件处理函数实现对XML文档的访问,因而SAX接口也被称作事件驱动接口。
局限性:
1. SAX分析器在对XML文档进行分析时,触发了一系列的事件,由于事件触发本身是有时序性的,因此,SAX提供的是一种顺序访问机制,对于已经分析过的部分,不能再倒回去重新处理。
即,一旦经过了某个元素,我们没有办法返回去再去访问它。
2. SAX分析器只做了一些简单的工作,大部分工作还要由应用程序自己去做。
也就是说,SAX分析器在实现时,只是顺序地检查XML文档中的字节流,判断当前字节是XML语法中的哪一部分、是否符合XML语法,然后再触发相应的事件,而事件处理函数本身则要由应用程序自己来实现。
同DOM分析器相比,SAX分析器缺乏灵活性。
优势:
然而,由于SAX分析器实现简单,对内存要求比较低,(SAX不必将整个XML文档加载到内存当中,因此它占据内存要比DOM小), 因此实现效率比较高。
对于大型的XML文档来说,通常会用SAX而不是DOM。
并且对于那些只需要访问XML文档中的数据而不对文档进行更改的应用程序来说,SAX分析器更为合适。
SAX分析器
XML解析器实际上就是一段代码,它读入一个XML文档并分析其结构。
分类:
带校验的解析器
不校验的解析器(效率高)
支持DOM的解析器(W3C的官方标准)
支持SAX的解析器(事实上的工业标准)
SAX是事件驱动的,文档的读入过程就是SAX的解析过程。
在读入的过程中,遇到不同的项目,解析器会调用不同的处理方法。
【XML文件内容】
<?xml version = "1.0" encoding = "UTF-8"?> <students> <student id="1"> <name>tonyliu</name> <age>19</age> <sex>男</sex> <group> <id>1</id> <name>Group1</name> </group> </student> <student id="2"> <name>Susywang</name> <age>18</age> <sex>女</sex> <group> <id>2</id> <name>Group2</name> </group> </student> <student id="3"> <name>JackZhang</name> <age>35</age> <sex>男</sex> <group> <id>3</id> <name>Group3</name> </group> </student> </students>
【SAX 解析代码】
根据xml文件内容分为2个类,一个是Student类另一个为Group类,分别记入相对应的字段。
再定义一个枚举,分别存入一些常量,方便调用。这里要注意的是,仔细看一下xml内容,会发现Student以及group类中都包含了id跟name的字段。所以当我们在写代码中为了让计算机清楚的知道我们指的name,id到底是Student中的还是Group中的,要通过Enum定义的常量来区分。
package Entity; public class Student { private int id; private String name; private int age; private String sex; private Group group; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Group getGroup() { return group; } public void setGroup(Group group) { this.group = group; } public Student(int id, String name, int age, String sex, Group group) { super(); this.id = id; this.name = name; this.age = age; this.sex = sex; this.group = group; } public Student() { super(); // TODO Auto-generated constructor stub } @Override public String toString() { return "Student [age=" + age + ", group=" + group + ", id=" + id + ", name=" + name + ", sex=" + sex + "]"; } }
package Entity; public class Group { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Group() { super(); // TODO Auto-generated constructor stub } public Group(int id, String name) { super(); this.id = id; this.name = name; } @Override public String toString() { return "Group [id=" + id + ", name=" + name + "]"; } }
package Tool; public enum StudentEnum { stuName,age,sex,groupId,groupName,none }
package Tool; import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import Entity.Group; import Entity.Student; //继承DefaultHandler这个类,为了使用startDocument,startElement,charecter,endelement,enddocument方法 public class StudentParser extends DefaultHandler{ private List<Student> students = null; //创建一个集合方便存放数据 private boolean isgroupState = false; //定义标签区分group以及student中的id,name这样的相同字段。因为只有2个类因此boolean类型即可 private StudentEnum state = StudentEnum.none; //把枚举的状态设置为none private Group group = null; //初始化Group类 private Student student = null; //初始化Student类 //设定一个集合方法为了方便外层调用students这个集合中的内容 public List<Student> getResult(){ return this.students; } @Override public void startDocument() throws SAXException { students = new ArrayList<Student>(); //初始化集合 } @Override public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException { //通过startElement方法中的qName来指定xml内容中的student字段 if(qName.equals("student")){ String stuId = attrs.getValue("id"); //<Student>的属性只有一个id,因此通过attrs.getValue这个方法获取到id属性 student = new Student(); //很重要,很重要,很重要。这里一定要初始化Student这个类,为了就是下面通过.setId方法来指定id值 student.setId(Integer.parseInt(stuId)); } if(qName.equals("name")){ //这里就看出来Enum的重要性了。如果是我们上面设定的isgroupState的话就调用Enum类中的group相关字段。相反就调用Enum类中Student相关常量 if(isgroupState) state = StudentEnum.groupName; else state = StudentEnum.stuName; return; } if(qName.equals("age")){ state = StudentEnum.age; //把状态标注为枚举中的age return; } if(qName.equals("sex")){ state = StudentEnum.sex; //同上 return; } if(qName.equals("group")){ group = new Group(); //如果为group那么先要初始化Group这个类,方便调用group类中的方法 isgroupState = true; //因为默认isgroupState的状态为false。这里如果是group那么自然状态就变成了true return; } //group类中只有2个字段,而name在我们上述中已经有了判断。那么id跟Student一样先判断qName是否是id,再判断是否是groupState中的id,如果是就把state状态标注成Enum中的groupId. if(qName.equals("id")){ if(isgroupState){ state = StudentEnum.groupId; return; } } } @Override public void characters(char[] ch, int start, int length) throws SAXException { if(state == StudentEnum.none){ return; } //定义一个String类,存放characters方法中给出了三个参数. String str = new String(ch,start,length); //通过switch ,case分别把每个字段记录进去并通过student,group类中的set方法来进行设定。设定完毕后一定要把每个状态标注成none switch (state) { case stuName: student.setName(str); state = StudentEnum.none; break; case age: student.setAge(Integer.parseInt(str)); state = StudentEnum.none; break; case sex: student.setSex(str); state = StudentEnum.none; break; case groupId: group.setId(Integer.parseInt(str)); state = StudentEnum.none; break; case groupName: group.setName(str); state = StudentEnum.none; break; default: break; } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { //这里是指在什么节点结束。xml内容我们是以</group></Student>为结束的因此这里还是根据qName来判断并标注状态。 if(qName.equals("group")){ isgroupState = false; student.setGroup(group); //通过student类中的.setGroup方法把group中的内容追加到Student类中 return; } if(qName.equals("student")){ students.add(student); //最后把student类中的内容全部加载到students这个集合中 return; } } @Override public void endDocument() throws SAXException { // TODO Auto-generated method stub super.endDocument(); } }
StudentParser这个类中内容是我们SAX解析中的重点。同DOM一样也是一个解析器,通过逐步读取数据的方式进行解析不需要一下子把数据读取到内容中后再解析。
【Test测试】
package View; import java.io.IOException; import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; import Entity.Student; import Tool.StudentParser; public class Test { public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
//调用SAXParserFactory以及SAXParer工厂类。 SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); StudentParser handler = new StudentParser(); //初始化我们的解析器 parser.parse("C:/Users/IBM_ADMIN/Desktop/xml/student.xml", handler);//通过parser.parse方法指定文件地址以及加载我们的解析器 List<Student> students = handler.getResult(); //调用解析中的.getResult方法来调用students集合中的内容。 //循环这个集合并打印 for(Student stu:students){ System.out.println(stu); } } }
【结果】
下面结果可以看出我们成功的对XML文件中的内容进行了解析。日后比如说有个Group4,并有了新的成员的情况下,我们也不需要再重写我们的代码。只要xml结构不变,那么内容也会自动的被读取。
Student [age=19, group=Group [id=1, name=Group1], id=1, name=tonyliu, sex=男] Student [age=18, group=Group [id=2, name=Group2], id=2, name=Susywang, sex=女] Student [age=35, group=Group [id=3, name=Group3], id=3, name=JackZhang, sex=男]