Android之SAX、DOM和Pull解析XML

Android之SAX、DOM和Pull解析XML

文章链接:http://blog.csdn.net/qq_16628781/article/details/70147230

知识点

  1. XML的3种解析方式:SAX、DOM和Pull;
  2. PULL解析XML文档示例;
  3. Dom解析XML文档示例;
  4. SAX解析XML文档示例;
  5. 调用运行结果示例;
  6. 新名词记录{SAX、DOM和Pull解析XML文档}

概述

XML的解析有3中解析方式:SAX、DOM、Pull。

SAX(Simple API for XML) 使用流式处理的方式,它并不记录所读内容的相关信息。它是一种以事件为驱动的XML API,解析速度快,占用内存少。使用回调函数来实现。 缺点是不能倒退。

DOM(Document Object Model) 是一种用于XML文档的对象模型,可用于直接访问XML文档的各个部分。它是一次性全部将内容加载在内存中,生成一个树状结构,它没有涉及回调和复杂的状态管理。 缺点是加载大文档时效率低下。

Pull内置于Android系统中。也是官方解析布局文件所使用的方式。Pull与SAX有点类似,都提供了类似的事件,如开始元素和结束元素。不同的是,SAX的事件驱动是回调相应方法,需要提供回调的方法,而后在SAX内部自动调用相应的方法。而Pull解析器并没有强制要求提供触发的方法。因为他触发的事件不是一个方法,而是一个数字。它使用方便,效率高。

SAX、DOM、Pull的比较:
1. 内存占用:SAX、Pull比DOM要好;
2. 编程方式:SAX采用事件驱动,在相应事件触发的时候,会调用用户编好的方法,也即每解析一类XML,就要编写一个新的适合该类XML的处理类。DOM是W3C的规范,Pull简洁。
3. 访问与修改:SAX采用流式解析,DOM随机访问。
4. 访问方式:SAX,Pull解析的方式是同步的,DOM逐字逐句。

下面是每个解析方式的使用方法讲解示例。


XML文档和实体类

首先是XML文档users.xml

<?xml version="1.0" encoding="UTF-8" ?>
<users>
    <user id="1">
    <userName>yaoj</userName>
    <password>123456</password>
    <age>24</age>
    </user>
<user id="2">
<userName>tanksu</userName>
<password>3333333</password>
<age>23</age>
    </user>
<user id="3">
<userName>fishing</userName>
<password>666666</password>
<age>26</age>
    </user>
</users>

因为要解析成对象,那么就必须要有对应的实体类。

public class UserBean implements Serializable {

    //串行化版本统一标识符
    private static final long serialVersionUID = 1L;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    private int id;
    private String userName;
    private transient String password;
    private int age;

    public static class InstanceHolder {
        private static final UserBean userBean = new UserBean("tanksu", "999999", 12);
    }

    private Object readResolve() throws ObjectStreamException {
        return InstanceHolder.userBean;
    }

    public UserBean() {
    }

    public UserBean(String userName, String password, int age) {
        this.userName = userName;
        this.password = password;
        this.age = age;
    }

    //初始化version版本号
    private final long version = 2L;

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        if (version == 1L) {
            out.writeObject(password);
        } else if (version == 2L) {
            out.writeObject(password);
        } else {
            out.writeObject(password);
        }
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        password = (String) in.readObject();
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUserName() {

        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "{" +
                "id:" + id +
                ", userName:" + userName +
                ", password:" + password +
                ", age:" + age +
                ", version:" + version +
                "}";
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

上面的实体类请忽略version字段,因为这个类是项目中其它用处,如果要解析,那么直接在对应级别上面加入一个判断就可以了。


PULL

在Android中,自带的解析方法就是pull。它的优点在于便捷高效。下面我们直接看代码:

/**
     * 根据pull解析XML文档
     *
     * @param inputStream 需要解析的输入流
     * @return 返回解析后的集合,可能为空
     */
    public static List<UserBean> parseXmlByPull(InputStream inputStream) {
        XmlPullParser pullParser = Xml.newPullParser();
        List<UserBean> userBeanList = null;
        try {
            pullParser.setInput(inputStream, "UTF-8");
            //START_TAG, END_TAG, TEXT等等的节点
            int eventType = pullParser.getEventType();
            UserBean userBean = null;
            //当还没有解析到结束文档的节点,一直循环
            while (eventType != XmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                    case XmlPullParser.START_DOCUMENT: //开始解析文档,最外层的节点<users>
                        userBeanList = new ArrayList<>();
                        break;
                    case XmlPullParser.START_TAG: //开始解析到节点,需要对每一个定义的节点进行处理
                        if ("user".equals(pullParser.getName())) {
                            userBean = new UserBean();
                            int id = Integer.parseInt(pullParser.getAttributeValue(0));
                            userBean.setId(id);
                        } else if ("userName".equals(pullParser.getName())) {
                            if (userBean != null) userBean.setUserName(pullParser.nextText());
                        } else if ("passwor".equals(pullParser.getName())) {
                            if (null != userBean) userBean.setPassword(pullParser.nextText());
                        } else if ("age".equals(pullParser.getName())) {
                            if (null != userBean)
                                userBean.setAge(Integer.parseInt(pullParser.nextText()));
                        }
                        break;
                    case XmlPullParser.END_TAG: //结束节点,将解析的userbean加入到集合中
                        if ("user".equals(pullParser.getName())) {
                            userBeanList.add(userBean);
                            userBean = null;
                        }
                        break;
                    case XmlPullParser.END_DOCUMENT:
                        break;
                }
                //获取到下一个节点,在触发解析动作
                eventType = pullParser.next();
            }
        } catch (XmlPullParserException | IOException e) {
            e.printStackTrace();
        }
        return userBeanList;
    }

解释:首先取得XmlPullParser类的一个实例,然后将输入流进行解析,解析的结果会放到pullParser里面,获取到eventType,这里的eventType对应的分别有文档开始(START_DOCUMENT)、文档结束(END _DOCUMENT)、节点开始(START _TAG)、节点结束(END _TAG)等;然后根据每个不同的节点或者文档结束,取出不同的数值,设置给
userBean,最后加入到返回的集合里面去。


Dom

DOM最初在HTML语言里面认识到,因为网页也是使用可扩展语言XML来写的,最初浏览器加载一个网页的时候,就是将整个网页都加入到内存里面进行解析。这样做的不好就是,可能我只是需要其中某些个数据,但是你都加载到内存了,这就造成了浪费。

废话不多说,我们直接看如何利用DOM解析XML文档。

/**
     * 根据dom解析XML文档
     *
     * @param inputStream 需要解析的输入流
     * @return 返回解析后的集合,可能为空
     */
    public static List<UserBean> parseUserByDom(InputStream inputStream) {
        List<UserBean> userBeanList = new ArrayList<>();
        try {
            //生成一个文档工厂类
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            //
            DocumentBuilder builder = factory.newDocumentBuilder();

            //开始解析输入流,因为dom解析是首先将整个文档读入内存,所以这里就完成了解析
            //下面是对这个文档取出操作
            Document document = builder.parse(inputStream);
            //获取到文档的根节点<users>
            Element root = document.getDocumentElement();
            //获取到所有下一级节点<user>集合
            //开始解析<user>节点及其子节点
            NodeList nodeList = root.getElementsByTagName("user");
            for (int i = 0; i < nodeList.getLength(); i++) {
                Element element = (Element) nodeList.item(i);
                //获取<user>的id属性
                int id = Integer.parseInt(element.getAttribute("id"));
                UserBean userBean = new UserBean();
                userBean.setId(id);
                //获取<user>节点下的子节点,并且遍历判断,取值
                NodeList childNodes = element.getChildNodes();
                for (int y = 0; y < childNodes.getLength(); y++) {
                    if (childNodes.item(y).getNodeType() == Node.ELEMENT_NODE) {
                        if ("userName".equals(childNodes.item(y).getNodeName())) {
                            String name = childNodes.item(y).getFirstChild().getNodeValue();
                            userBean.setUserName(name);
                        } else if ("password".equals(childNodes.item(y).getNodeName())) {
                            String password = childNodes.item(y).getFirstChild().getNodeValue();
                            userBean.setPassword(password);
                        } else if ("age".equals(childNodes.item(y).getNodeName())) {
                            String age = childNodes.item(y).getFirstChild()
                                    .getNodeValue();
                            userBean.setAge(Integer.parseInt(age));
                        }
                    }
                }
                //当一个<user>节点被解析完,那么加入和集合中
                userBeanList.add(userBean);
            }
            inputStream.close();
        } catch (ParserConfigurationException | IOException | SAXException e) {
            e.printStackTrace();
        }
        return userBeanList;
    }

解释:开始新建一个DocumentBuilder实体,进行解析输入的XML文档,然后获取到根节点,再获取到根节点下面所有的子节点,然后利用循环,对每一个< user>进行解析,包括其下面的各个子节点。如果子节点下面还有节点,那么也需要element.getChildNodes()方法获取到子节点的列表,在循环取值。如果还有更深的结构,同理可得。最后将获取的数值设置给实体,加入到返回集合里面去即可。


SAX

SAX方法会比较麻烦一点,因为它需要你继承DefaultHandler类,然后重写相对应节点触发的方法。和pull方法类似,不同的是API帮你分类好,到了那个节点,就会调用哪一个方法,而不用自己去判断。

看下面的示例:

/**
 * desc:SAX解析XML的处理类
 * <p>
 * author:kuyu.yaojt (tanksu)
 * <p>
 * email:yaojt@kuyumall.com
 * <p>
 * blog:http://blog.csdn.net/qq_16628781
 * <p>
 * date:17/4/12
 */

public class SAXUtil extends DefaultHandler {
    //解析的集合
    private List<UserBean> userBeanList = new ArrayList<>();
    //通过此变量,记录当前标签的名称
    private String curTagName;
    UserBean userBean; //记录当前Person

    /**
     * 提供给外部调用的方法
     *
     * @param inputStream 输入流
     * @return 解析的实体类集合
     */
    public static List<UserBean> parse(InputStream inputStream) throws Exception {
        //创建SAXParserFactory解析工厂类
        SAXParserFactory factory = SAXParserFactory.newInstance();
        SAXUtil saxUtil = new SAXUtil();
        //实例化一个SAXParser解析类
        SAXParser saxParser = factory.newSAXParser();
        //开始解析文档
        //参数2:利用我们定义的handler进行解析输入的文档
        saxParser.parse(inputStream, saxUtil);
        return saxUtil.getUserBeanList();
    }

    private SAXUtil() {
    }

    private List<UserBean> getUserBeanList() {
        return userBeanList;
    }

    //开始解析文档,做初始化工作
    @Override
    public void startDocument() throws SAXException {
        userBeanList = new ArrayList<>();
        super.startDocument();
    }

    //开始解析元素
    @Override
    public void startElement(String uri, String localName, String qName,
                             Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        //uri:命名空间的uri,如果没有,则为""
        //localName标签名
        // fullName带命名空间的标签名
        // attribute存放该标签所有属性
        //attributes:该元素所对应的的所有属性
        if ("user".equals(localName)) {
            for (int i = 0; i < attributes.getLength(); i++) {
                userBean = new UserBean();
                //根据index拿到属性的name,因为可能不止一个属性
                String localAttributeName = attributes.getLocalName(i);
                if ("id".equals(localAttributeName)) {
                    //根据属性名拿到值
                    String id = attributes.getValue(uri, localAttributeName);
                    userBean.setId(Integer.valueOf(id));
                }
            }
        }
        curTagName = localName;
    }

    //解析字符
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        //根据char数组生产一个字符串
        String sData = new String(ch, start, length).trim();
        if ("userName".equals(curTagName)) {
            userBean.setUserName(sData);
        } else if ("password".equals(curTagName)) {
            userBean.setPassword(sData);
        } else if ("age".equals(curTagName)) {
            userBean.setAge(Integer.parseInt(sData));
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        if ("user".equals(localName)) {
            //当一个用户被解析完,加入到集合中
            userBeanList.add(userBean);
            userBean = null;
        }
        curTagName = null;
    }

    @Override
    public void endDocument() throws SAXException {
        super.endDocument();
    }

}

解释:这里的逻辑可以参照pull解析,不多讲。主要提一下,因为没有类中没有返回当前节点名称的方法,所以需要自己记录。上面的类,我私有化了构造方法,并且提供了一个静态的方法来使用sax解析XML文档。


解析XML文档

我们这直接调用上面的几个方法:

InputStream inputStream = getResources().openRawResource(R.raw.users);
        List<UserBean> userBeanList = ParseUtil.parseXmlByPull(inputStream);
        userBeanList = ParseUtil.parseUserByDom(inputStream);
        try {
            userBeanList = SAXUtil.parse(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (UserBean userBean : userBeanList){
            CommonLog.logInfo("-=-=-=>>", userBean.toString());
        }

因为都是解析同一个XML文档,解析出来的结果都是一样的。所以下面只给出一个结果图。

这里写图片描述


总结

总的来说,3中解析XML的方法各有优劣。使用方法大同小异,只要理解了XML文档的结构,使用哪一种方法都不是问题了。但是不推荐使用DOM,因为他是全部读入内存来解读,而且解析的代码感觉不是很清晰。推荐使用pull或者sax都可以。

以上就是所有内容,如有任何问题,请及时与我联系,谢谢。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值