簡體   English   中英

如何提高 Java 中的 StAX xml 解析器速度?

[英]How to improve StAX xml parser speed in java?

我有一個使用StAX的 XML 解析器,我用它來解析一個巨大的文件。 但是,我想盡可能縮短時間。 我正在讀取將其放入數組中的值並將其發送到另一個函數進行評估。 我正在調用displayName標記,它應該在獲取名稱后立即轉到下一個 xml,而不是讀取整個 xml 文件。 我正在尋找最快的方法。

爪哇:


import java.io.File;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Iterator;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.*;

public class Driver {

    private static boolean bname;

    public static void main(String[] args) throws FileNotFoundException, XMLStreamException {

        File file = new File("C:\\Users\\Robert\\Desktop\\root\\SDKCode\\src\\main\\java\\com\\example\\xmlClass\\data.xml");


        parser(file);
    }

    public static void parser(File file) throws FileNotFoundException, XMLStreamException {

        bname = false;


        XMLInputFactory factory = XMLInputFactory.newInstance();


        XMLEventReader eventReader = factory.createXMLEventReader(new FileReader(file));


        while (eventReader.hasNext()) {

            XMLEvent event = eventReader.nextEvent();

            // This will trigger when the tag is of type <...>
            if (event.isStartElement()) {
                StartElement element = (StartElement) event;


                Iterator<Attribute> iterator = element.getAttributes();
                while (iterator.hasNext()) {
                    Attribute attribute = iterator.next();
                    QName name = attribute.getName();
                    String value = attribute.getValue();
                    System.out.println(name + " = " + value);
                }


                if (element.getName().toString().equalsIgnoreCase("displayName")) {
                    bname = true;
                }

            }


            if (event.isEndElement()) {
                EndElement element = (EndElement) event;


                if (element.getName().toString().equalsIgnoreCase("displayName")) {
                    bname = false;
                }


            }


            if (event.isCharacters()) {
                // Depending upon the tag opened the data is retrieved .
                Characters element = (Characters) event;

                if (bname) {
                    System.out.println(element.getData());
                }

            }
        }
    }
}

XML:

<?xml version="1.0" encoding="UTF-8"?>
<results
        xmlns="urn:www-collation-com:1.0"
        xmlns:coll="urn:www-collation-com:1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:www-collation-com:1.0
              urn:www-collation-com:1.0/results.xsd">

    <WebServiceImpl array="1"
        guid="FFVVRJ5618KJRHNFUIRV845NRUVHR" xsi:type="coll:com.model.topology.app.web.WebService">
        <isPlaceholder>false</isPlaceholder>
        <displayName>server.servername1.siqom.siqom.us.com</displayName>
        <hierarchyType>WebService</hierarchyType>
        <hierarchyDomain>app.web</hierarchyDomain>
    </WebServiceImpl>
</results>

<?xml version="1.0" encoding="UTF-8"?>
<results
        xmlns="urn:www-collation-com:1.0"
        xmlns:coll="urn:www-collation-com:1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:www-collation-com:1.0
              urn:www-collation-com:1.0/results.xsd">

    <WebServiceImpl array="1"
        guid="FFVVRJ5618KJRHNFUIRV845NRUVHR" xsi:type="coll:com.model.topology.app.web.WebService">
        <isPlaceholder>false</isPlaceholder>
        <displayName>server.servername2.siqom.siqom.us.com</displayName>
        <hierarchyType>WebService</hierarchyType>
        <hierarchyDomain>app.web</hierarchyDomain>
    </WebServiceImpl>
</results>

<?xml version="1.0" encoding="UTF-8"?>
<results
        xmlns="urn:www-collation-com:1.0"
        xmlns:coll="urn:www-collation-com:1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:www-collation-com:1.0
              urn:www-collation-com:1.0/results.xsd">

    <WebServiceImpl array="1"
        guid="FFVVRJ5618KJRHNFUIRV845NRUVHR" xsi:type="coll:com.model.topology.app.web.WebService">
        <isPlaceholder>false</isPlaceholder>
        <displayName>server.servername3.siqom.siqom.us.com</displayName>
        <hierarchyType>WebService</hierarchyType>
        <hierarchyDomain>app.web</hierarchyDomain>
    </WebServiceImpl>
</results>


etc...

有幾種方法可以前進。

拆分文件

首先,如果您的大文件實際上是幾個串聯的 XML 文件(如您所展示的示例),那么這個大文件不是(有效的)XML 文件,我建議在處理到嚴格的 XML 解析庫(Stax、DOM、 SAX、XSL 等等……)。

一個有效的 XML 文件只有一個序言和一個根元素。

您可以使用 XML prolog 作為分割標記,使用純 IO/字節級 API(不涉及 XML)。

然后可以將每個拆分視為單個 XML“文件”(如果需要,可以獨立處理,用於多線程)。 我的意思不是字面上的“文件”,它可能是從原始“大文件”中拆分出來的一大塊byte[]

加速 XML 解析

關於你的代碼

使用XMLEventReader ,您的示例代碼中有一些突出的地方。

  1. 你不應該像你那樣迭代一個屬性。 除非我遺漏了什么,否則您不會對這次迭代做任何事情。
  2. 一旦您處於localNamedisplayNameSTART_ELEMENT ,您應該調用getElementText ,它是解析器內部的,具有一些 while 循環無法實現的速度優化技巧。 此調用將使閱讀器END_ELEMENT在匹配的END_ELEMENT ,因此實際上,您大大簡化了代碼(僅檢查displayName START_ELEMENT ,僅此而已)。
  3. 您的 XML 似乎格式良好,因此您可以在找到結果后立即跳過解析
  4. XMLInputFactories旨在重用,因此不要為每個文件創建一個,而是創建一個共享實例。
  5. XML(xxx)Reader是可關閉的,所以關閉它們。
  6. 一些 XML 庫具有 JDK 提供的更快的字符解碼方案(知道 XML 編碼的內部允許它們這樣做),因此如果您有一個有效的 XML prolog 在文件開頭對編碼進行了標記,您應該為您的工廠提供一個File對象或一個InputStream ,而不是一個Reader

切換到XMLStreamReader

除此之外,使用XMLStreamReader可以獲得比XMLEventReader更快的性能。 這是因為XMLEvent實例代價高昂,這要歸功於即使創建它們的解析器已經移動,它們也能保持可用。 這意味着XMLEvent是相對重量級的,它在創建時保存所有可能的相關信息(命名空間上下文、所有屬性等),這具有構建成本和保存在內存中的成本。

解析完成后,可以緩存和引用事件。

XMLStreamReader不發出任何事件,所以不付出這個代價。 看到您只需要讀取一個文本值並且解析后沒有使用XMLEvent ,流讀取器將產生更好的性能。

切換到更快的XMLStreamReader

上次我檢查(有點太久以前)時, Woodstox比 JDK 標准 Stax 實現(派生自 Apache Xerces)快得多。 周圍可能有更快的孩子

嘗試其他 XML 嗎?

我非常懷疑您是否會從任何其他解析技術中獲得更快的性能(SAX 通常是等效的,但您實際上不必選擇在找到相關標簽后立即退出解析)。 XSLT 速度相當快,但它顯示出的強大功能伴隨着性能代價(通常會構建某種輕量級 DOM 樹)。 XPath 也是如此,表達式的表現力通常意味着某種復雜的結構被保留在下面。 當然,DOM 通常要慢得多。

不做 XML 怎么樣?

它可能只應作為最后的手段使用,如果所有其他優化都已被取消,並且您知道 XML 處理是瓶頸(不是 IO,不是其他任何東西,只是 XML 處理和本身)。

正如@MichaelKay 在評論中指出的那樣,不使用 XML 工具在未來的任何時候都可能會中斷,因為文件的創建方式雖然在 XML 中完全等效,但可能會演變並破壞基於簡單文本的工具。

使用純粹基於文本的工具,您可能會被命名空間聲明的更改、不同的換行符、HTML 實體編碼、外部引用和許多其他特定於 XML 的細節所迷惑,以獲得額外性能的一小部分。

多線程處理你的進程

使用多線程可能是一種解決方案,但並非沒有警告。

如果您的進程在典型的 EE 服務器實現中運行,具有高級配置和任何類型的適當負載,多線程並不總是勝利,因為系統可能已經缺乏產生額外線程的資源,和/或您可能正在失敗的內部優化服務器通過在其托管設施之外創建線程。

如果您的進程是所謂的輕量級應用程序,或者如果其典型用法只需要幾個用戶同時使用它,則不太可能遇到此類問題,您可能會考慮生成一個ExecutorService來並行執行 XML 解析.

要考慮的另一件事是 IO。 單個文件的 XML 處理,CPU 方面,應該盡可能多地從解析的並行化中獲益。 但是您可能會遇到流程的其他部分(通常是 IO)的瓶頸。 如果您在單個 CPU 中解析 XML 的速度比從磁盤中提取數據的速度快,那么並行化就沒有用了,您將有許多線程在等待磁盤,這可能會使您的系統餓死(如果有的話) . 所以你必須相應地調整。

改變過程

如果您在一個工作單元中一直在閱讀“大文件”或數千個小文件,那么這可能是退后一步查看您的過程的好機會。

  1. 讀取數千個小文件在 IO 和系統調用方面會產生成本,這實際上是阻塞調用 您的 java 進程必須等待來自系統級內容的數據。 如果你有辦法減少系統調用的數量(打開更少的文件,使用更大的緩沖區......)這可能是一個勝利。 我的意思是:讀取單個tar 文件(包含 2000 個小 xml - 幾個 kbs - 文件)通常比讀取 2000 個單個文件更快。

  2. 先發制人/即時完成工作。 為什么要等到用戶要求數據來解析 XML? 一旦數據到達系統(可能是異步的?),就不可能解析它。 這將為您省去從磁盤讀取數據的麻煩,並且可能讓您有機會插入一個無論如何都會解析文件的進程,從而在兩種情況下都節省時間。 然后,當用戶請求出現時,您只需要查詢結果(在各種數據庫中)?

往前走

不測量東西就無法建立性能。

所以:測量。

IO成本是多少?

XML 處理成本是多少? 它的哪一部分? (在您的示例代碼中,每個文件的 XMLInputFactory` 只是無用的初始化意味着如果您剛剛使用分析器對其進行了測量,則可以獲得很多收益)

您的服務呼叫中的其他內容需要多少錢? (您是否在通話之前/之后連接到數據庫?在每個文件中?可以以不同的方式完成)。

如果您仍然被卡住,您可以使用這些發現編輯您的問題,以獲得進一步的幫助。

由於我可以看到多個 xml 文件進行解析,因此您可以使用多線程一次解析 3 個 xml 文件,並將對象存儲在線程安全列表(如 CopyOnWriteArrayList)或線程安全映射(如並發哈希映射)中。 如果您使用 Stax 解析器進行解析,它已經進行了優化,可用於更大的 xml 文件。 此外,如果您不需要來自 XMl 的所有數據,您可以使用 XPath,同樣 XPath 和 Streaming XML 解析是不同的。

數字在哪里? 沒有測量就無法解決性能問題。 你實現了什么性能? 它是長期惡化,還是已經接近您可以合理預期的最佳狀態?

我在您的代碼中只能看到一個性能“錯誤”,那就是為每個文件創建一個新的解析器工廠(創建工廠非常昂貴,它涉及檢查類路徑上的每個 JAR)。 但是你讓我感到困惑:你說你正在解析一個巨大的文件(“巨大的”實際上是什么意思?)但你所展示的似乎是許多小的 XML 文檔的串聯。 從性能的角度來看,這兩個用例有很大的不同:對於很多小文檔,初始化解析器通常是總成本的很大一部分。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM