XML解析(一),SAX解析XML

转载自  XML解析(一),SAX解析XML

一、概述

 SAX,全称Simple API for XML,是一种以事件驱动的XMl API,是XML解析的一种新的替代方法,解析XML常用的还有DOM解析,PULL解析(Android特有),SAX与DOM不同的是它边扫描边解析,自顶向下依次解析,由于边扫描边解析,所以它解析XML具有速度快,占用内存少的优点,对于Android等CPU资源宝贵的移动平台来说是一个巨大的优势。

  • SAX的优点:

    1. 解析速度快
    2. 占用内存少
  • SAX的缺点:

    1. 无法知道当前解析标签(节点)的上层标签,及其嵌套结构,仅仅知道当前解析的标签的名字和属性,要知道其他信息需要程序猿自己编码
    2. 只能读取XML,无法修改XML
    3. 无法随机访问某个标签(节点)
  • SAX解析适用场合 
    1. 对于CPU资源宝贵的设备,如Android等移动设备
    2. 对于只需从xml读取信息而无需修改xml

二、SAX解析的步骤

解析步骤很简单,可分为以下四个步骤

  1. 得到xml文件对应的资源,可以是xml的输入流,文件和uri
  2. 得到SAX解析工厂(SAXParserFactory
  3. 由解析工厂生产一个SAX解析器(SAXParser
  4. 传入输入流和handler给解析器,调用parse()解析

知道了SAX解析的优缺点和解析步骤,下面我们通过一个简单的Demo学习一下SAX解析XML

三、SAX解析实战

新建一个Android工程叫SaxParseXmlDemo,将sax.jar下载放到工程的lib下面并添加到构建路径中,为了方便,我先将工程的目录结构列一下:

1、新建一个xml文件叫users.xml

<?xml version="1.0" encoding="UTF-8"?>
<users><user id="1"><name>毕向东</name><password>bxd123</password></user><user id="2"><name>韩顺平</name><password>hsp123</password></user><user id="3"><name>马士兵</name><password>msb123</password></user>
</users>

2、新建一个JavaBean

package com.example.saxparsexmldemo;public class User {private long id;private String name;private String password;public long getId() {return id;}public void setId(long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

3、新建一个类XmlParseHandler.java,该类需要继承DefaultHandler或者实现ContentHandler接口,这里我们通过继承DefaultHandler(实现了ContentHandler接口)的方式,该类是SAX解析的核心所在,我们要重写以下几个我们关心的方法。

  • startDocument():文档解析开始时调用,该方法只会调用一次
  • startElement(String uri, String localName, String qName, 
    Attributes attributes):标签(节点)解析开始时调用

    1. uri:xml文档的命名空间
    2. localName:标签的名字
    3. qName:带命名空间的标签的名字
    4. attributes:标签的属性集
  • characters(char[] ch, int start, int length):解析标签的内容的时候调用

    1. ch:当前读取到的TextNode(文本节点)的字节数组
    2. start:字节开始的位置,为0则读取全部
    3. length:当前TextNode的长度
  • endElement(String uri, String localName, String qName):标签(节点)解析结束后调用
  • endDocument():文档解析结束后调用,该方法只会调用一次

 重写startDocument(),我们在这里初始化User集合,该集合用来存放解析出来的user

Log.e("startDocument", "startDocument()");
users = new ArrayList<User>();

 重写startElement,在startElement中先判断当前的标签是否user,如果是user标签则说明接下来是一个user的信息,所以我们新建一个User对象用来存放这个user的信息,在这里我们得到当前user标签的id属性,封装到user对象中。并记录当前的标签

        Log.e("startElement", localName + "-startElement()");if ("user".equals(localName)) { // 是一个用户for (int i = 0; i < attributes.getLength(); i++) {Log.e("attributes", "attribute_name:" + attributes.getLocalName(i)+ "  attribute_value:" + attributes.getValue(i));user = new User();if("id".equals(attributes.getLocalName(i))){user.setId(Long.parseLong(attributes.getValue(i)));}}}currentTag = localName; // 把当前标签记录下来

 重写characters,在characters中解析出当前标签的内容,如果当前标签为name标签,则解析name标签的内容封装到当前User对象的name属性中,如果当前标签为password标签,则解析password标签的内容封装到当前User对象的password属性中

        String value = new String(ch,start,length); // 将当前TextNode转换为StringLog.e("characters", value+"");if("name".equals(currentTag)){  // 当前标签为name标签,该标签无子标签,直接将上面获取到的标签的值封装到当前User对象中// 该节点为name节点user.setName(value);}else if("password".equals(currentTag)){  // 当前标签为password标签,该标签无子标签,直接将上面获取到的标签的值封装到当前User对象中// 该节点为password节点user.setPassword(value);}

 重写endElement,在这个方法中判断当前是否是user标签的结束,如果是user标签结束,则这个user信息解析结束,并将当前的User对象和当前的标签重置

        Log.e("endElement", localName + "-endElement()");if("user".equals(localName)){users.add(user);user = null;}currentTag = null;

 重写endDocument,这里直接给个空实现,我们只需观察Log输出

Log.e("endDocument",  "-endDocument()");

XmlParseHandler.java完整代码:

package com.example.saxparsexmldemo;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 android.util.Log;public class XmlParseHandler extends DefaultHandler {private List<User> users;private String currentTag; // 记录当前解析到的节点名称private User user; // 记录当前的user/*** 文档解析结束后调用*/@Overridepublic void endDocument() throws SAXException {super.endDocument();Log.e("endDocument",  "-endDocument()");}/*** 节点解析结束后调用* @param uri : 命名空间的uri* @param localName : 标签的名称* @param qName : 带命名空间的标签名称*/@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {super.endElement(uri, localName, qName);Log.e("endElement", localName + "-endElement()");if("user".equals(localName)){users.add(user);user = null;}currentTag = null;}/*** 文档解析开始调用*/@Overridepublic void startDocument() throws SAXException {super.startDocument();Log.e("startDocument", "startDocument()");users = new ArrayList<User>();}/*** 节点解析开始调用* @param uri : 命名空间的uri* @param localName : 标签的名称* @param qName : 带命名空间的标签名称*/@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {super.startElement(uri, localName, qName, attributes);Log.e("startElement", localName + "-startElement()");if ("user".equals(localName)) { // 是一个用户for (int i = 0; i < attributes.getLength(); i++) {Log.e("attributes", "attribute_name:" + attributes.getLocalName(i)+ "  attribute_value:" + attributes.getValue(i));user = new User();if("id".equals(attributes.getLocalName(i))){user.setId(Long.parseLong(attributes.getValue(i)));}}}currentTag = localName; // 把当前标签记录下来}@Overridepublic void characters(char[] ch, int start, int length)throws SAXException {super.characters(ch, start, length); String value = new String(ch,start,length); // 将当前TextNode转换为StringLog.e("characters", value+"");if("name".equals(currentTag)){  // 当前标签为name标签,该标签无子标签,直接将上面获取到的标签的值封装到当前User对象中// 该节点为name节点user.setName(value);}else if("password".equals(currentTag)){  // 当前标签为password标签,该标签无子标签,直接将上面获取到的标签的值封装到当前User对象中// 该节点为password节点user.setPassword(value);}}public List<User> getUsers() {return users;}
}

4、新建一个类XmlParseUtils.java,写一个方法进行xml解析

public static List<User> getUsers() throws ParserConfigurationException, SAXException, IOException {// 加载文件返回文件的输入流InputStream is = XmlParseUtils.class.getClassLoader().getResourceAsStream("users.xml");XmlParseHandler handler = new XmlParseHandler();// 1. 得到SAX解析工厂SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();// 2. 让工厂生产一个sax解析器SAXParser newSAXParser = saxParserFactory.newSAXParser();// 3. 传入输入流和handler,解析newSAXParser.parse(is, handler);is.close();return handler.getUsers();}

 其中第三步也可以通过XMLReader来完成

XMLReader xmlReader = newSAXParser.getXMLReader();
InputSource input = new InputSource(is);
xmlReader.parse(input );

到这里SAX解析XML的代码完成了,OK,万事具备,只欠测试了

这里我们用Android的单元测试,只需完成调用XmlParseUtilsgetUsers()方法测试输出即可

Android的单元测试(需要连接模拟器或者手机)
  • 环境搭建

    • 在 AndroidManifest.xml的根节点下面添加一个instrumentation

      <instrumentation android:targetPackage="com.example.saxparsexmldemo" android:name="android.test.InstrumentationTestRunner"></instrumentation>
    • 在 AndroidManifest.xml的application节点下面添加uses-library

      <uses-library android:name="android.test.runner"/>

  • com.example.saxparsexmldemo下面新建一个测试类SAXParseXmlTest.java,写一个测试方法
    public void testSAXgetUsers() throws Exception{List<User> users = XmlParseUtils.getUsers();for(User user : users){Log.e(TAG, "name:"+user.getName());Log.e(TAG, "password:"+user.getPassword());}}

 OK,右键该类的testSAXgetUsers,Run As Android JUnit Test,我们可以看到XML解析成功

通过上面的这个小Demo的完成,我们可以看到SAX解析代码不多也不难理解,关键是Handler的几个方法的使用,即遇到什么符号会触发什么事件,以及触发过程,掌握了SAX的事件触发,那么就掌握了SAX解析XML,下面我们来分析一下SAX解析的原理或流程

四、SAX解析XML原理

在分析SAX解析原理之前,我们先看一下上面的demo的日志输出

以users.xml的解析过程为例,结合上面的日志输出,我们可以分析出SAX解析的流程

1、xml解析开始,startDocument被调用,这个方法在整个xml解析过程中调用了一次,所以我们可以在这个方法里面初为解析XML做一些准备,比如初始化变量 
2、解析每遇到一个标签都会经历startElement-characters-endElement这个过程,即每一个标签都会触发startElement-characters-endElement 
3、通过users这个根节点的解析,user标签解析以及name,password标签解析过程,我们知道user标签是users的子标签,name和password标签是user标签的子标签,我们知道当解析一个标签的时候,如果该标签有子标签,则先回调用该标签的startElement方法,这里面可以先得到该标签的属性信息,然后触发characters解析该标签的内容(值),然后子标签触发startElement-characters-endElement(子标签触发),最后该标签触发endElement,该标签解析结束

下面画一个图让我们进一步理解SAX解析XML的原理:

对上图做个简单说明,当当前标签有子标签的时候,该标签先触发characters,然后子标签触发startElement-characters-endElement事件,这个过程可以理解为一个不断递归的过程。

OK,SAX解析原理分析完了,最后用一句话描述SAX解析过程

startDocument-startElement-characters-endElement-endDocument

总结,SAX解析XML具有解析速度快,占用内存少,对于Android等移动设备来说有巨大的优势,深入了解SAX的事件触发机制是掌握SAX解析的关键,掌握了SAX的事件触发就掌握了SAX解析XML

上面这篇文章由于个人理解,如果有理解错的地方,欢迎大家指出,与君共勉,一起进步。

Demo下载地址:http://download.csdn.net/detail/ydxlt/9328309

下篇文章:【XML解析(二)】DOM解析XML


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328244.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

go编译库给c语言函数返回值,go语言 函数return值的几种情况

IOS开发基础知识--碎片361:tabBarController跳转到另一个一级页面 当我们用tabBarController时,若已经到其中一个TabBar的子页,又要跳转到某一个一级的页面时,可以这样写 //这样就可以避免跳 ...MapReduce的MapTask任务的运行源码级分析TaskTracker任务初始化及启动task源码级分…

编程语言的发展趋势及未来方向(3):函数式编程

关于声明式编程的还有一部分重要的内容&#xff0c;那便是函数式编程。函数式编程已经有很长时间的历史了&#xff0c;当年LISP便是个函数式编程语言。除了LISP以外我们还有其他许多函数式编程语言&#xff0c;如APL、Haskell、Scheme、ML等等。关于函数式编程在学术界已经有过…

idea中@Data标签getset不起作用

原 idea中Data标签getset不起作用 2017年06月08日 11:22:40 seapeak007 阅读数 27070 spring cloud中使用Data标签&#xff0c;不用手动添加get set方法&#xff0c;但是如果项目中其他类中使用getset方法&#xff0c;如果报错&#xff0c;原因是idea中没有添加Lombok插件&a…

浅析SAX,DOM,JAXP,JDOM与DOM4J之间的关系

转载自 浅析SAX,DOM,JAXP,JDOM与DOM4J之间的关系众所周知&#xff0c;SAX与DOM是JAVA中两大核心XML解析API类库&#xff0c;而JAXP,JDOM与DOM4J都是基于这两大核心API而衍生出来的。今日兴起看了看相关资料&#xff0c;写篇文章总结总结^.^。 SAX与DOM 首先需要说明白的是SAX…

调用反射类的方法

package org.entity;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;/*** 本案例演示如何通过反射将字符串转换为类* */ public class Test2 {public static void main(String[] args) {String user "org.entity.User";//字…

android 新的布局,Android新布局方式ConstraintLayout快速入门教程

前言在android开发中&#xff0c;我们通常是手写布局&#xff0c;很少会用拖动来写布局&#xff0c;虽然constraintlayout在i/o上以拖动来展现了各种功能&#xff0c;我估计在以后开发中&#xff0c;程序员还是习惯手撸代码。我自己试着拖着用了一下&#xff0c;用得不是很明白…

编程语言的发展趋势及未来方向(2):声明式编程与DSL

这里先从声明式&#xff08;Declarative&#xff09;编程谈起。 目前我们在编写软件时大量使用的是命令式&#xff08;Imperative&#xff09;编程语言&#xff0c;例如C#&#xff0c;Java或是C等等。这些语言的特征在于&#xff0c;写出的代码除了表现出“什么&#xff08;Wha…

HtmlParser提取网页中的纯文本信息

转载自 HtmlParser提取网页中的纯文本信息HTMLParser 一个解析web页面的开源类库。 准备学习下搜索方面的技术&#xff0c;就学习了些网络爬虫的知识。最近一直在一个点上困惑&#xff0c;如何提取一个网页上的纯文本信息。要使用正则表达式的话呢&#xff0c;需要考…

编程语言的发展趋势及未来方向(1):历史回顾及趋势概述

大家好&#xff0c;我是Anders Hejlsberg&#xff0c;现在是微软的Technical Fellow&#xff0c;担任C#编程语言的首席架构师&#xff0c;也参与并领导.NET Framework以及各种语言的开发。我现在打算谈一下……实际上是我脑海中一些影响未来5到10年编程语言设计的内容。比如C#或…

调用反射类的指定方法

package org.entity;import java.lang.reflect.Method;/*** 本案例演示如何通过反射将字符串转换为类* */ public class Test2_2 {public static void main(String[] args) {String user "org.entity.User";//字符串是该类的全限定名try {Class clzz Class.forName…

springboot创建项目 编写dao serviece 和controller 持久层用mybatis

11 刷新一下pom 万一没有引入进去 jpa和mybatis选择哪个&#xff1f; 1.看领导要求 2.都会最好 多学点是没错的

android查看wifi是否双频,Android判断wifi是5G还是2.4G

我一开始看这帖子&#xff0c;找不到答案&#xff0c;为了后来的人&#xff0c;我来回复吧。WifiManager wifiManager (WifiManager) getSystemService(Context.WIFI_SERVICE);WifiInfo wifiInfo wifiManager.getConnectionInfo();根据wifiInfo.getFrequency()可以判断是不是…

java过滤html标签获取纯文本信息

转载自 java过滤html标签获取纯文本信息 package com.lyt.base.util;import java.util.regex.Pattern;public class FilterHtmlUtil {public static String Html2Text(String inputString){String htmlStr inputString; //含html标签的字符串String textStr "";java…

综述:编程语言的发展趋势及未来方向

程序设计离不开编程语言&#xff0c;但是编程语言在国内的大环境中似乎一直是个二等公民。国内的计算机教育和工程培训&#xff0c;似乎一直在宣传“语言不重要&#xff0c;重要的是思想”&#xff0c;“语言一通百通”等观点&#xff0c;甚至在许多人眼中“语言的讨论”完全是…

IntelliJ IDEA: 无法创建Java Class文件

IntelliJ IDEA: 无法创建Java Class文件 2018年06月21日 22:49:44 dandelion9508 阅读数 11864 今天打开项目时&#xff0c;发现右击新建不了java.class文件&#xff0c;于是尝试了以下方法&#xff1a; &#xff08;1&#xff09;选择 File——>Project Structure——>…

iphone android互传文件夹,安卓和苹果手机怎么互传文件_安卓与苹果手机之间互传文件的方法教程_3DM手游...

一直以来&#xff0c;很多的app都是分为安卓和IOS两个版本&#xff0c;在使用上&#xff0c;两个平台之间很多功能都不能跨平台使用。比如大家在玩游戏时充值的东西&#xff0c;在苹果充值之后&#xff0c;同一个账号转到安卓手机上玩&#xff0c;那些东西都是不能迁移进来的。…

jsoup怎么获取两个标签之间的text?

转载自 jsoup怎么获取两个标签之间的text? 这是开发者工具解析到的一个论坛页面结构。。可以看到每一个a标签和br标签之间总会夹着一个text,,也就是密码文本。而我根据网上的API教程&#xff1a;siblingA ~ siblingX: 查找A元素之前的同级X元素&#xff0c;比如&#xff1a…

编程语言的发展趋势及未来方向(7):总结

OK&#xff0c;我想现在已经讲的差不多了&#xff0c;我来做个总结吧。 在我看来&#xff0c;对于编程语言来说&#xff0c;现在出现了许多有趣的东西&#xff0c;也是令人激动的时刻。在过去&#xff0c;大约1995-2005年&#xff0c;的确可以说是一个有些特别的编程语言的黄金…

通过反射获取方法返回的类型

package org.entity;import java.lang.reflect.Method; import java.lang.reflect.Type;/*** 本案例演示如何通过反射将字符串转换为类* */ public class Test3 {public static void main(String[] args) {String user "org.entity.User";//字符串是该类的全限定名t…

android 按键消息,在android中模拟键盘消息(shell命令的方法)

已找到解决方案[code]use: sendeventcommand format: sendeventdevice type code value[command] [device] [type] [code] [value]sendevent/dev/input/event0 1 229 1/dev/input/event0 is the device to send it to[type] 1 is unknow for me ( maybe code for physical butt…