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


<users>
    <user id="1">
    <userName>yaojuserName>
    <password>123456password>
    <age>24age>
    user>
<user id="2">
<userName>tanksuuserName>
<password>3333333password>
<age>23age>
    user>
<user id="3">
<userName>fishinguserName>
<password>666666password>
<age>26age>
    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 parseXmlByPull(InputStream inputStream) {
        XmlPullParser pullParser = Xml.newPullParser();
        List 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: //开始解析文档,最外层的节点
                        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 parseUserByDom(InputStream inputStream) {
        List userBeanList = new ArrayList<>();
        try {
            //生成一个文档工厂类
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            //
            DocumentBuilder builder = factory.newDocumentBuilder();

            //开始解析输入流,因为dom解析是首先将整个文档读入内存,所以这里就完成了解析
            //下面是对这个文档取出操作
            Document document = builder.parse(inputStream);
            //获取到文档的根节点
            Element root = document.getDocumentElement();
            //获取到所有下一级节点集合
            //开始解析节点及其子节点
            NodeList nodeList = root.getElementsByTagName("user");
            for (int i = 0; i < nodeList.getLength(); i++) {
                Element element = (Element) nodeList.item(i);
                //获取的id属性
                int id = Integer.parseInt(element.getAttribute("id"));
                UserBean userBean = new UserBean();
                userBean.setId(id);
                //获取节点下的子节点,并且遍历判断,取值
                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));
                        }
                    }
                }
                //当一个节点被解析完,那么加入和集合中
                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的处理类
 * 

* author:kuyu.yaojt (tanksu) *

* email:[email protected] *

* blog:http://blog.csdn.net/qq_16628781 *

* date:17/4/12 */ public class SAXUtil extends DefaultHandler { //解析的集合 private List userBeanList = new ArrayList<>(); //通过此变量,记录当前标签的名称 private String curTagName; UserBean userBean; //记录当前Person /** * 提供给外部调用的方法 * * @param inputStream 输入流 * @return 解析的实体类集合 */ public static List 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 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 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都可以。

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

你可能感兴趣的:(android工具)