淺談 Java XML 底層解析方式

   XML 使用DTD(document type definition)文檔類型來標記數據和定義數據,格式統一且跨平臺和語言,已成爲業界公認的標準。java

   目前 XML 描述數據龍頭老大的地位漸漸受到 Json 威脅。經手項目中,模塊/系統之間交互數據方式有 XML 也有 Json,說不上孰好孰壞。node

   XML 規整/有業界標準/很容易和其餘外部的系統進行交互,Json 簡單/靈活/佔帶寬比小。git

   仁者見仁智者見智,項目推動中描述數據方式須要根據具體場景拿捏。dom

   這篇博客主要描述目前 Java 中比較主流的 XML 解析底層方式,給須要這方面項目實踐的同窗一些參考。ide

   Demo 項目 git 地址:https://git.oschina.net/LanboEx/xml-parse-demo.git模塊化

 

   Sax/Satx/Dom 由國外開源社區或組織貢獻,Sun 從新組織起名 JAXP 自JDK 1.6 起陸續將他們添加進去。性能

   Xmlpull在 JDK 中沒有看到它的身影,若是須要使用它,你須要添加額外的類庫。ui

   Jdom/Dom4j/Xstream... 是基於這些底層解析方式從新組織封裝的開源類庫,提供簡潔的 API,有機會再寫一篇博客描述。this

   Dom4j 是基於 JAXP 解析方式,性能優異、功能強大、極易使用的優秀開源類庫。spa

   Jdom 若是你細看內部代碼,其實也是基於 JAXP 但具體包結構被從新組織, API 大量使用了 Collections 類,在性能上被 dm4j 壓了好幾個檔次。

   實例 Demo 中須要解析的 xml 文件以下,中規中矩不簡單,也不復雜,示例業務場景都能將節點的值解析出來,組成業務實體對象。

<?xml version="1.0"?>
<classGrid>
    <classGridlb>
        <class_id>320170105000009363</class_id>
        <class_number>0301</class_number>
        <adviser>018574</adviser>
        <studentGrid>
            <studentGridlb>
                <stu_id>030101</stu_id>
                <stu_name>齊天</stu_name>
                <stu_age>9</stu_age>
                <stu_birthday>2008-11-07</stu_birthday>
            </studentGridlb>
            <studentGridlb>
                <stu_id>030102</stu_id>
                <stu_name>張惠</stu_name>
                <stu_age>10</stu_age>
                <stu_birthday>2009-04-08</stu_birthday>
            </studentGridlb>
            <studentGridlb>
                <stu_id>030103</stu_id>
                <stu_name>龍五</stu_name>
                <stu_age>9</stu_age>
                <stu_birthday>2008-11-01</stu_birthday>
            </studentGridlb>
        </studentGrid>
    </classGridlb>
    <classGridlb>
        <class_id>420170105000007363</class_id>
        <class_number>0302</class_number>
        <adviser>018577</adviser>
        <studentGrid>
            <studentGridlb>
                <stu_id>030201</stu_id>
                <stu_name>馬寶</stu_name>
                <stu_age>10</stu_age>
                <stu_birthday>2009-09-02</stu_birthday>
            </studentGridlb>
        </studentGrid>
    </classGridlb>
</classGrid>
Demo.xml

1. 基於樹或基於對象模型

   官方 W3C 標準,以層次結構組織的節點或信息片段的集合。容許在樹中尋找特定信息,分析該結構一般須要加載整個文檔和構造層次結構。

   最先的一種解析模型,加載整個文檔意味着在大文件 XMl 會遇到性能瓶頸。

   Dom 解析代碼示例:

        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); }

   Dom 解析方式有個最大的優勢能夠在任什麼時候候在樹中上下導航,獲取和操做任意部分的數據。

2. 流事件分析中推模型

   靠事件驅動的模型,當它每發現一個節點就引起一個事件,須要編寫這些事件的處理程序。

   這樣的作法很麻煩,並且不靈活,主流的分析方式有 Xmlpull和 JAXP 中的 Sax。

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

        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(); }

   Xmlpull爲接口層,xpp3_min 爲實現層,其實能夠引入另外自帶接口層 xpp3 版本。

 <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>

   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 相似。 

public class SAXHandler extends DefaultHandler { private List<StudentGridlb> studentGridlbList = null; private StudentGridlb studentGridlb = null; private String tagName; @Override public void startDocument() throws SAXException { System.out.println("---->startDocument() is invoked..."); studentGridlbList = new ArrayList<>(); } @Override public void endDocument() throws SAXException { System.out.println("---->endDocument() is invoked..."); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { System.out.println("-------->startElement() is invoked...,qName:" + qName); if ("studentGridlb".equals(qName)) { this.studentGridlb = new StudentGridlb(); } this.tagName = qName; } @Override public void endElement(String uri, String localName, String qName) throws SAXException { System.out.println("-------->endElement() is invoked..."); if (qName.equals("studentGridlb")) { this.studentGridlbList.add(this.studentGridlb); } this.tagName = null; } @Override public void characters(char[] ch, int start, int length) throws SAXException { System.out.println("------------>characters() is invoked..."); if (this.tagName != null) { String contentText = new String(ch, start, length); if (this.tagName.equals("stu_id")) { this.studentGridlb.setStu_id(contentText); } if (this.tagName.equals("stu_name")) { this.studentGridlb.setStu_name(contentText); } if (this.tagName.equals("stu_age")) { this.studentGridlb.setStu_age(Integer.parseInt(contentText)); } if (this.tagName.equals("stu_birthday")) { DateFormat format = new SimpleDateFormat("yyyy-MM-dd"); try { this.studentGridlb.setStu_birthday(format.parse(contentText)); } catch (ParseException e) { e.printStackTrace(); } } } } public List<StudentGridlb> getStudentGridlbList() { return studentGridlbList; } public void setStudentGridlbList(List<StudentGridlb> studentGridlbList) { this.studentGridlbList = studentGridlbList; } }
SaxHandler

   推模式不須要等待全部數據都被處理,分析就能當即開始;

   只在讀取數據時檢查數據,不須要保存在內存中;

   能夠在某個條件獲得知足時中止解析,沒必要解析整個文檔;

   效率和性能較高,能解析大於系統內存的文檔;

   固然缺點也很突出例如須要本身負責TAG的處理邏輯(例如維護父/子關係等),使用麻煩;

   單向導航,很難同時訪問同一文檔的不一樣部分數據,不支持 XPath;

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

  在遍歷文檔時,把感興趣的部分從讀取器中拉出,不須要引起事件,容許咱們選擇性地處理節點;

  大大提升了靈活性,以及總體效率,拉模式中比較常見 stax,stax 提供了兩套 API 共使用。

  stax demo(基於光標的方式解析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();

  stax demo(基於迭代方式解析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();

   基於指針的 stax API,這種方式儘管效率高,但沒有提供 XML 結構的抽象,所以是一種低層 API。

   stax 基於迭代器的 API 是一種面向對象的方式,這也是它與基於指針的 API 的最大區別。

   基於迭代器的 API 只須要肯定解析事件的類型,而後利用其方法得到屬於該事件對象的信息。

   經過將事件轉變爲對象,讓應用程序能夠用面向對象的方式處理,有利於模塊化和不一樣組件之間的代碼重用。

   Ok,這篇博客對 java 底層解析 xml 方式作了點總結。其實在實際項目中,上述幾種方式解析 xml 編寫起來都很費事。

   都會引入封裝起來的穩定開源庫,如 Dom4j/Jdom/Xstream.....,這些類庫屏蔽了底層複雜的部分,呈現給咱們簡潔明瞭的 API;

   但若是公司業務複雜程度已經遠遠超出了開源類庫的提供的範疇,不妨本身依賴底層解析技術本身造輪子。

相關文章
相關標籤/搜索