Java Xml 底层解析方式

XML 使用DTD(document type definition)文档类型来标记数据和定义数据,格式统一且跨平台和语言,已成为业界公认的标准。

   目前 XML 描述数据龙头老大的地位渐渐受到 Json 威胁。经手项目中,模块/系统之间交互数据方式有 XML 也有 Json,说不上孰好孰坏。

   XML 规整/有业界标准/很容易和其他外部的系统进行交互,Json 简单/灵活/占带宽比小。

   仁者见仁智者见智,项目推进中描述数据方式需要根据具体场景拿捏。

   这篇博客主要描述目前 Java 中比较主流的 XML 解析底层方式,给需要这方面项目实践的同学一些参考。

   Demo 项目 git 地址:https://git.oschina.net/LanboEx/xml-parse-demo.git

Java Xml 底层解析方式

 

   sax/satx/dom 由国外开源社区或组织贡献,Sun 重新组织起名 JAXP 自JDK 1.6 起陆续将他们添加进去。

   xmlpull 在 JDK 中没有看到它的身影,如果需要使用它,你需要添加额外的类库。

   jdom/dom4j/xstream... 是基于这些底层解析方式重新组织封装的开源类库,提供简介的 API,有机会再写一篇博客描述。

   dom4j 是基于 JAXP 解析方式,性能优异、功能强大、极易使用的优秀开源类库。

   jdom 如果你细看内部代码,其实也是基于 JAXP 但具体包结构被重新组织, API 大量使用了 Collections 类,在性能上被 dm4j 压了好几个档次。

   实例 Demo 中需要解析的 xml 文件如下,中规中矩不简单,也不复杂,示例业务场景都能将节点的值解析出来,组成业务实体对象。

Java Xml 底层解析方式 Demo.xml
回到顶部

1. 基于树或基于对象模型

   官方 W3C 标准,以层次结构组织的节点或信息片断的集合。允许在树中寻找特定信息,分析该结构通常需要加载整个文档和构造层次结构。

   最早的一种解析模型,加载整个文档意味着在大文件 XMl 会遇到性能瓶颈。

   dom 解析代码示例:

Java Xml 底层解析方式
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();//得到DOM解析器的工厂实例
        DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();//从DOM工厂中获得DOM解析器
        Document doc = dbBuilder.parse(Thread.currentThread().getContextClassLoader().getResource("demo.xml").getPath());
        NodeList nList = doc.getElementsByTagName("studentGridlb");

        List<StudentGridlb> studentGridlbList = new ArrayList<>();
        for (int i = 0; i < nList.getLength(); i++) {
            StudentGridlb studentGridlb = new StudentGridlb();
            NodeList childNodes = nList.item(i).getChildNodes();
            for (int k = 0; k < childNodes.getLength(); k++) {
                if (childNodes.item(k) instanceof Element) {
                    Element element = (Element) childNodes.item(k);
                    if ("stu_id".equals(element.getNodeName())) {
                        studentGridlb.setStu_id(element.getTextContent());
                    }
                    if ("stu_name".equals(element.getNodeName())) {
                        studentGridlb.setStu_name(element.getTextContent());
                    }
                    if ("stu_age".equals(element.getNodeName())) {
                        studentGridlb.setStu_age(Integer.parseInt(element.getTextContent()));
                    }
                    if ("stu_birthday".equals(element.getNodeName())) {
                        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                        studentGridlb.setStu_birthday(format.parse(element.getTextContent()));
                    }
                }
            }
            studentGridlbList.add(studentGridlb);
        }
Java Xml 底层解析方式

   dom 解析方式有个最大的优点可以在任何时候在树中上下导航,获取和操作任意部分的数据。

回到顶部

2. 流事件分析中推模型

   靠事件驱动的模型,当它每发现一个节点就引发一个事件,需要编写这些事件的处理程序。

   这样的做法很麻烦,而且不灵活,主流的分析方式有 xmlpull 和 JAXP 中的 sax。

   xmlpull demo (引入 xmlpull.jar  xpp3_min.jar]):

Java Xml 底层解析方式
        XmlPullParserFactory pullParserFactory = XmlPullParserFactory.newInstance();
        XmlPullParser pullParser = pullParserFactory.newPullParser();//获取XmlPullParser的实例
        pullParser.setInput(Thread.currentThread().getContextClassLoader().getResourceAsStream("demo.xml"), "UTF-8");

        int event = pullParser.getEventType();
        List<StudentGridlb> studentGridlbList = new ArrayList<>();
        StudentGridlb studentGridlb = new StudentGridlb();

        while (event != XmlPullParser.END_DOCUMENT) {
            String nodeName = pullParser.getName();
            switch (event) {
                case XmlPullParser.START_DOCUMENT:
                    System.out.println("xmlpull 解析 xml 开始:");
                    break;
                case XmlPullParser.START_TAG:
                    if ("stu_id".equals(nodeName)) {
                        studentGridlb.setStu_id(pullParser.nextText());
                    }
                    if ("stu_name".equals(nodeName)) {
                        studentGridlb.setStu_name(pullParser.nextText());
                    }
                    if ("stu_age".equals(nodeName)) {
                        studentGridlb.setStu_age(Integer.parseInt(pullParser.nextText()));
                    }
                    if ("stu_birthday".equals(nodeName)) {
                        DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                        studentGridlb.setStu_birthday(format.parse(pullParser.nextText()));
                    }
                    break;
                case XmlPullParser.END_TAG:
                    if ("studentGridlb".equals(nodeName)) {
                        studentGridlbList.add(studentGridlb);
                        studentGridlb = new StudentGridlb();
                    }
                    break;
            }
            event = pullParser.next();
        }
Java Xml 底层解析方式

   xmlpull 为接口层,xpp3_min 为实现层,其实可以引入另外自带接口层 xpp3 版本。

Java Xml 底层解析方式
        <dependency>
            <groupId>xmlpull</groupId>
            <artifactId>xmlpull</artifactId>
            <version>1.1.3.1</version>
        </dependency>

        <dependency>
            <groupId>xpp3</groupId>
            <artifactId>xpp3_min</artifactId>
            <version>1.1.4c</version>
        </dependency>
Java Xml 底层解析方式

   sax demo:

        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); //获取SAX分析器的工厂实例,专门负责创建SAXParser分析器
        SAXParser saxParser = saxParserFactory.newSAXParser();
        InputStream inputStream = new FileInputStream(new File(Thread.currentThread().getContextClassLoader().getResource("demo.xml").getPath()));

        SAXHandler xmlSAXHandler = new SAXHandler();
        saxParser.parse(inputStream, xmlSAXHandler);

   sax 解析时还需要单独编写时间响应 Handler ,和集合排序时实现的Comparator 类似。 

Java Xml 底层解析方式 SAXHandler

   推模式不需要等待所有数据都被处理,分析就能立即开始;

   只在读取数据时检查数据,不需要保存在内存中;

   可以在某个条件得到满足时停止解析,不必解析整个文档;

   效率和性能较高,能解析大于系统内存的文档;

   当然缺点也很突出例如需要自己负责TAG的处理逻辑(例如维护父/子关系等),使用麻烦;

   单向导航,很难同时访问同一文档的不同部分数据,不支持 XPath;

回到顶部

3. 流事件分析中的拉模型

  在遍历文档时,把感兴趣的部分从读取器中拉出,不需要引发事件,允许我们选择性地处理节点;

  大大提高了灵活性,以及整体效率,拉模式中比较常见 stax,stax 提供了两套 API 共使用。

  stax demo(基于光标的方式解析XML):

Java Xml 底层解析方式
        InputStream stream = new FileInputStream(Thread.currentThread().getContextClassLoader().getResource("demo.xml").getPath());
        XMLInputFactory factory = XMLInputFactory.newInstance();
        XMLStreamReader parser = factory.createXMLStreamReader(stream);
        List<StudentGridlb> studentGridlbList = new ArrayList<>();
        StudentGridlb studentGridlb =  null;
while (parser.hasNext()) { int event = parser.next(); if (event == XMLStreamConstants.START_DOCUMENT) { System.out.println("stax 解析xml 开始....."); } if (event == XMLStreamConstants.START_ELEMENT) { if (parser.getLocalName().equals("stu_id")) { studentGridlb = new StudentGridlb(); studentGridlb.setStu_id(parser.getElementText()); } else if (parser.getLocalName().equals("stu_name")) { studentGridlb.setStu_name(parser.getElementText()); } else if (parser.getLocalName().equals("stu_age")) { studentGridlb.setStu_age(Integer.parseInt(parser.getElementText())); } else if (parser.getLocalName().equals("stu_birthday")) { DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); studentGridlb.setStu_birthday(format.parse(parser.getElementText())); } } if (event == XMLStreamConstants.END_ELEMENT) { if (parser.getLocalName().equals("studentGridlb")) { studentGridlbList.add(studentGridlb); } } if (event == XMLStreamConstants.END_DOCUMENT) { System.out.println("stax 解析xml 结束....."); } } parser.close();
Java Xml 底层解析方式

  stax demo(基于迭代方式解析XML):

Java Xml 底层解析方式
        XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
        XMLEventReader xmlEventReader = xmlInputFactory.createXMLEventReader(Thread.currentThread().getContextClassLoader().getResourceAsStream("demo.xml"));

        List<StudentGridlb> studentGridlbList = new ArrayList<>();
        StudentGridlb studentGridlb = null;

        while (xmlEventReader.hasNext()) {
            XMLEvent event = xmlEventReader.nextEvent();
            if (event.isStartElement()) {
                String name = event.asStartElement().getName().toString();
                if (name.equals("stu_id")) {
                    studentGridlb = new StudentGridlb();
                    studentGridlb.setStu_id(xmlEventReader.getElementText());
                } else if (name.equals("stu_name")) {
                    studentGridlb.setStu_name(xmlEventReader.getElementText());
                } else if (name.equals("stu_age")) {
                    studentGridlb.setStu_age(Integer.parseInt(xmlEventReader.getElementText()));
                } else if (name.equals("stu_birthday")) {
                    DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                    studentGridlb.setStu_birthday(format.parse(xmlEventReader.getElementText()));
                }
            }

            if (event.isEndElement()) {
                String name = event.asEndElement().getName().toString();
                if (name.equals("studentGridlb")) {
                    studentGridlbList.add(studentGridlb);
                }
            }
        }
        xmlEventReader.close();
Java Xml 底层解析方式

   基于指针的 stax API,这种方式尽管效率高,但没有提供 XML 结构的抽象,因此是一种低层 API。

   stax 基于迭代器的 API 是一种面向对象的方式,这也是它与基于指针的 API 的最大区别。

   基于迭代器的 API 只需要确定解析事件的类型,然后利用其方法获得属于该事件对象的信息。

   通过将事件转变为对象,让应用程序可以用面向对象的方式处理,有利于模块化和不同组件之间的代码重用。

   Ok,这篇博客对 java 底层解析 xml 方式做了点总结。其实在实际项目中,上述几种方式解析 xml 编写起来都很费事。

   都会引入封装起来的稳定开源库,如 dom4j/jdom/xstream.....,这些类库屏蔽了底层复杂的部分,呈现给我们简洁明了的 API;

   但如果公司业务复杂程度已经远远超出了开源类库的提供的范畴,不妨自己依赖底层解析技术自己造*。