淺談 Java Xml 底層解析方式

来源:http://www.cnblogs.com/java-class/archive/2017/05/16/6857863.html
-Advertisement-
Play Games

XML 使用DTD(document type definition)文檔類型來標記數據和定義數據,格式統一且跨平臺和語言,已成為業界公認的標準。 目前 XML 描述數據龍頭老大的地位漸漸受到 Json 威脅。經手項目中,模塊/系統之間交互數據方式有 XML 也有 Json,說不上孰好孰壞。 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

 

   sax/satx/dom 由國外開源社區或組織貢獻,Sun 重新組織起名 JAXP 自JDK 1.6 起陸續將他們添加進去。

   xmlpull 在 JDK 中沒有看到它的身影,如果需要使用它,你需要添加額外的類庫。

   jdom/dom4j/xstream... 是基於這些底層解析方式重新組織封裝的開源類庫,提供簡介的 API,有機會再寫一篇博客描述。

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

   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。

   xmlpull demo (引入 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;

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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 數組排序演算法:冒泡排序法、選擇排序法、直接插入排序法、快速排序法 ...
  • 雲服務 子系統:後臺管理系統、Restfu服務系統、Dubbo服務/管控/監控中心 Zookeeper註冊中心、報表分析系統、日誌記錄系統、定時調度系統 搜索引擎系統、分散式文件系統、消息系統、SSO單點登錄系統 SOA管控平臺、UI組件系統、OA辦公系統、CMS新聞發佈系統 支付系統、資料庫配置系 ...
  • dropzone.js預設是Ajax上傳圖片給伺服器,那麼如何獲取到圖片名呢?其實我們是可以通過dropzone的success函數獲取到伺服器返回的數據 dropzone.js在HTML的配置如下; ...
  • ———————————————————————————————————————————— 實驗7.4:RS232串口應用實例 設計要求: 通過串口發送接收數據(串口工作方式1) 實現思路: 定時器設置 串口設置 工作方式 波特率 開啟定時/計數器 串口執行 接收數據 發送數據 串口設置 工作方式 波 ...
  • 路由配置 路由組 允許共用路由屬性,例如中間件和命名空間等,我們沒有必要為每個路由單獨設置共有屬性,共有屬性會以數組的形式放到 Route::group 方法的第一個參數中。 中間件('middleware' => 'auth'),驗證用戶是否登錄。 命名空間 ('namespace' => 'Ad ...
  • spring mvc Spring框架(框架即:編程註解+xml配置的方式)MVC是Spring框架的一大特征,Spring框架有三大特征(IOC(依賴註入),AOP(面向切麵),MVC(建模M-視圖V-控制器C)。框架一般用於團隊開發,使用分層的方式使每個人完成不同的模塊,然後再組合在一起,使完成 ...
  • 變數 在程式設計中,變數(英語:Variable,scalar)是指一個包含部分已知或未知數值或資訊(即一個值)之儲存位址,以及相對應之符號名稱(識別字)。通常使用變數名稱參照儲存值;將名稱和內容分開能讓被使用的名稱獨立於所表示的精確訊息之外。電腦原始碼中的識別字能在執行期間綁扎一個值,且該變數的值 ...
  • 一、場景 之前做的電商平臺,用戶在收到貨之後,大部分都不會主動的點擊確認收貨,導致給商家結款的時候,商家各種投訴,於是就根據需求,要做一個訂單在發貨之後的x天自動確認收貨。所謂的訂單自動確認收貨,就是在在特定的時間,執行一條update語句,改變訂單的狀態。 二、思路 最笨重的做法,通過linux後 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...