上周末,我開始考慮如何以一種資源友好的方式處理大量XML數據。我要解決的主要問題是如何以塊的形式處理大型XML文件,同時提供上游/下游系統,需要處理一些數據。
當然,我已經使用JAXB技術已有幾年了。 使用JAXB的主要優點是可以加快產品上市時間; 如果擁有XML模式,則可以使用工具自動自動生成相應的Java域模型類(Eclipse Indigo,各種工具中的Maven jaxb插件,ant任務,僅舉幾例)。 然后,JAXB API提供Marshaller和Unmarshaller來編寫/讀取XML數據,從而映射Java域模型。
當JAXB的思想作為我的問題解決方案,我suddendlly意識到,JAXB保存在內存中的XML架構的整體客觀 ,所以一個顯而易見的問題是:“如何將我們的基礎設施應對大型XML文件(例如,在我的情況與一些元素> 100,000)是否要使用JAXB?”。 我可以簡單地生成一個大的XML文件,然后為其創建一個客戶端,并了解有關內存消耗的信息。
眾所周知,在Java中主要有兩種處理XML數據的方法:DOM和SAX。 使用DOM,XML文檔以樹的形式表示在內存中。 如果需要對樹節點進行點選訪問,或者需要編寫簡短的XML文檔,則DOM很有用。 另一方面,是一種事件驅動技術SAX,該技術當時將整個文檔解析為一個XML元素,并且對于每個XML重要事件,將回調“推”到Java客戶端,然后該Java客戶端處理它們(例如START_DOCUMENT,START_ELEMENT,END_ELEMENT等)。 由于SAX不會將整個文檔帶入內存,而是將類似游標的方法應用于XML處理,因此它不會消耗大量內存。 SAX的缺點是它會處理整個文檔的開始到結束; 這可能不一定是大型XML文檔所需要的。 例如,在我的場景中,我希望能夠傳遞給下游系統XML元素(但可用),但同時也許我一次只希望傳遞100個元素,實現某種分頁解。 從內存消耗的角度來看,DOM似乎過于苛刻,而SAX似乎可以滿足我的需求。
我記得讀過一些有關STax的知識,它是一種Java技術,它在實現RAM友好性的同時提供了拉XML元素 (而不是推送XML元素,例如SAX)的能力。 然后,我研究了該技術,并決定STax可能是我想要的折衷方案。 但是我想保留JAXB提供的簡單編程模型,所以我確實需要將兩者結合起來。 在調查STax時,我遇到了Woodstox。 這個開源項目有望成為比許多其他工具更快的XML解析器,因此我決定也將其包含在我的基準測試中。 現在,我具有創建基準的所有元素,以便在處理大型XML文檔時為我提供內存消耗和處理速度指標。
基準計劃
為了創建基準,我需要執行以下操作:
- 創建一個定義我的域模型的XML模式。 這將是JAXB創建Java域模型的輸入
- 創建代表該模型的三個大型XML文件,分別具有10,000 / 100,000 / 1,000,000元素
- 有一個純JAXB客戶端,它將完全在內存中解組大型XML文件
- 有一個STax / JAXB客戶端,它將SAX技術的低內存消耗與JAXB提供的簡便編程模型結合在一起
- 擁有一個具有與STax / JAXB客戶端相同特性的Woodstox / JAXB客戶端(簡而言之,我只是想更改底層的解析器,看看是否可以提高性能)
- 記錄內存消耗和處理速度(例如,每個解決方案以多快的速度使XML塊作為JAXB域模型類在內存中可用)
- 因為我們知道,一張圖片可以說一千個單詞,所以可以圖形方式顯示結果。
域模型XML模式
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://uk.co.jemos.integration.xml/large-file"
xmlns:tns="http://uk.co.jemos.integration.xml/large-file"
elementFormDefault="qualified"><complexType name="PersonType"><sequence><element name="firstName" type="string"></element><element name="lastName" type="string"></element><element name="address1" type="string"></element><element name="address2" type="string"></element><element name="postCode" type="string"></element><element name="city" type="string"></element><element name="country" type="string"></element></sequence><attribute name="active" type="boolean" use="required" /></complexType><complexType name="PersonsType"><sequence><element name="person" type="tns:PersonType" maxOccurs="unbounded" minOccurs="1"></element></sequence></complexType><element name="persons" type="tns:PersonsType"></element>
</schema>
我決定建立一個相對簡單的域模型,用XML元素代表人物及其姓名和地址。 我還想記錄一個人是否活躍。
使用JAXB創建Java模型
我是Maven的粉絲,并將其用作構建系統的默認工具。 這是我為此小基準定義的POM:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>uk.co.jemos.tests.xml</groupId><artifactId>large-xml-parser</artifactId><version>1.0.0-SNAPSHOT</version><packaging>jar</packaging><name>large-xml-parser</name><url>http://www.jemos.co.uk</url><properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.3.2</version><configuration><source>1.6</source><target>1.6</target></configuration></plugin><plugin><groupId>org.jvnet.jaxb2.maven2</groupId><artifactId>maven-jaxb2-plugin</artifactId><version>0.7.5</version><executions><execution><goals><goal>generate</goal></goals></execution></executions><configuration><schemaDirectory>${basedir}/src/main/resources</schemaDirectory><includeSchemas><includeSchema>**/*.xsd</includeSchema></includeSchemas><extension>true</extension><args><arg>-enableIntrospection</arg><arg>-XtoString</arg><arg>-Xequals</arg><arg>-XhashCode</arg></args><removeOldOutput>true</removeOldOutput><verbose>true</verbose><plugins><plugin><groupId>org.jvnet.jaxb2_commons</groupId><artifactId>jaxb2-basics</artifactId><version>0.6.1</version></plugin></plugins></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.3.1</version><configuration><archive><manifest><addClasspath>true</addClasspath><mainClass>uk.co.jemos.tests.xml.XmlPullBenchmarker</mainClass></manifest></archive></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>2.2</version><configuration><outputDirectory>${project.build.directory}/site/downloads</outputDirectory><descriptors><descriptor>src/main/assembly/project.xml</descriptor><descriptor>src/main/assembly/bin.xml</descriptor></descriptors></configuration></plugin></plugins></build><dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.5</version><scope>test</scope></dependency><dependency><groupId>uk.co.jemos.podam</groupId><artifactId>podam</artifactId><version>2.3.11.RELEASE</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.0.1</version></dependency><!-- XML binding stuff --><dependency><groupId>com.sun.xml.bind</groupId><artifactId>jaxb-impl</artifactId><version>2.1.3</version></dependency><dependency><groupId>org.jvnet.jaxb2_commons</groupId><artifactId>jaxb2-basics-runtime</artifactId><version>0.6.0</version></dependency><dependency><groupId>org.codehaus.woodstox</groupId><artifactId>stax2-api</artifactId><version>3.0.3</version></dependency></dependencies>
</project>
關于此pom.xml的幾點注意事項。
- 我使用Java 6,因為從版本6開始,Java包含了JAXB,DOM,SAX和STax的所有XML庫。
- 為了從XSD架構自動生成域模型類,我使用了出色的maven-jaxb2-plugin,它除其他外還允許獲得具有toString,equals和hashcode支持的POJO。
我還聲明了jar插件,以為基準創建可執行的jar,并聲明程序集插件以分發基準的可執行版本。 基準測試的代碼附在本文后,因此,如果您要自己構建并運行它,只需解壓縮項目文件,打開命令行并運行:
$ mvn全新安裝程序:
此命令會將* -bin。*文件放入目標/站點/下載文件夾中。 解壓縮您的首選項并運行基準測試(-Dcreate.xml = true將生成XML文件。如果您已經擁有這些文件(例如,第一次運行之后),則不要傳遞它:
$ java -jar -Dcreate.xml = true large-xml-parser-1.0.0-SNAPSHOT.jar
創建測試數據
為了創建測試數據,我使用了PODAM (一種Java工具,用數據自動填充POJO和JavaBean)。 代碼很簡單:
JAXBContext context = JAXBContext.newInstance("xml.integration.jemos.co.uk.large_file");Marshaller marshaller = context.createMarshaller();marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");PersonsType personsType = new ObjectFactory().createPersonsType();List<PersonType> persons = personsType.getPerson();PodamFactory factory = new PodamFactoryImpl();for (int i = 0; i < nbrElements; i++) {persons.add(factory.manufacturePojo(PersonType.class));}JAXBElement<PersonsType> toWrite = new ObjectFactory().createPersons(personsType);File file = new File(fileName);BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file), 4096);try {marshaller.marshal(toWrite, bos);bos.flush();} finally {IOUtils.closeQuietly(bos);}
XmlPullBenchmarker在?/ xml-benchmark下生成三個大??型XML文件:
- large-person-10000.xml(大約3M)
- large-person-100000.xml(大約30M)
- large-person-1000000.xml(大約300M)
每個文件如下所示:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persons xmlns="http://uk.co.jemos.integration.xml/large-file"><person active="false"><firstName>Ult6yn0D7L</firstName><lastName>U8DJoUTlK2</lastName><address1>DxwlpOw6X3</address1><address2>O4GGvxIMo7</address2><postCode>Io7Kuz0xmz</postCode><city>lMIY1uqKXs</city><country>ZhTukbtwti</country></person><person active="false"><firstName>gBc7KeX9Tn</firstName><lastName>kxmWNLPREp</lastName><address1>9BIBS1m5GR</address1><address2>hmtqpXjcpW</address2><postCode>bHpF1rRldM</postCode><city>YDJJillYrw</city><country>xgsTDJcfjc</country></person>[..etc]
</persons>
每個文件包含10,000 / 100,000 / 1,000,000 <person>元素。
運行環境
我在三種不同的環境中嘗試了基準測試器:
- Ubuntu 10(64位)在Windows 7 Ultimate 上作為虛擬機運行 ,具有CPU i5、750 @ 2.67GHz和2.66GHz,8GB RAM,其中4GB專用于VM。 JVM:1.6.0_25,熱點
- Windows 7 Ultimate ,用于托管上述VM,因此具有相同的處理器。 JVM,1.6.0_24,熱點
- Ubuntu 10、32 位 ,3GB RAM,雙核。 JVM,1.6.0_24,OpenJDK
XML解組
為了解組代碼,我使用了三種不同的策略:
- 純JAXB
- STAX + JAXB
- 伍德斯托克斯+ JAXB
純JAXB解組
我用來使用JAXB解組大型XML文件的代碼如下:
private void readLargeFileWithJaxb(File file, int nbrRecords) throws Exception {JAXBContext ucontext = JAXBContext.newInstance("xml.integration.jemos.co.uk.large_file");Unmarshaller unmarshaller = ucontext.createUnmarshaller();BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));long start = System.currentTimeMillis();long memstart = Runtime.getRuntime().freeMemory();long memend = 0L;try {JAXBElement<PersonsType> root = (JAXBElement<PersonsType>) unmarshaller.unmarshal(bis);root.getValue().getPerson().size();memend = Runtime.getRuntime().freeMemory();long end = System.currentTimeMillis();LOG.info("JAXB (" + nbrRecords + "): - Total Memory used: " + (memstart - memend));LOG.info("JAXB (" + nbrRecords + "): Time taken in ms: " + (end - start));} finally {IOUtils.closeQuietly(bis);}
}
該代碼使用單線解組每個XML文件:
JAXBElement<PersonsType> root = (JAXBElement<PersonsType>) unmarshaller.unmarshal(bis);
我還訪問了基礎PersonType集合的大小以“接觸”內存數據。 順便說一句,調試該應用程序顯示,在這行代碼之后,所有10,000個元素確實在內存中可用。
JAXB + STax
使用STax,我只需要使用XMLStreamReader,遍歷所有<person>元素,然后依次將每個元素傳遞給JAXB,以將其解組為PersonType域模型對象。 代碼如下:
// set up a StAX reader
XMLInputFactory xmlif = XMLInputFactory.newInstance();
XMLStreamReader xmlr = xmlif.createXMLStreamReader(new FileReader(file));
JAXBContext ucontext = JAXBContext.newInstance(PersonType.class);
Unmarshaller unmarshaller = ucontext.createUnmarshaller();
long start = System.currentTimeMillis();
long memstart = Runtime.getRuntime().freeMemory();
long memend = 0L;try {xmlr.nextTag();xmlr.require(XMLStreamConstants.START_ELEMENT, null, "persons");xmlr.nextTag();while (xmlr.getEventType() == XMLStreamConstants.START_ELEMENT) {JAXBElement<PersonType> pt = unmarshaller.unmarshal(xmlr,PersonType.class);if (xmlr.getEventType() == XMLStreamConstants.CHARACTERS) {xmlr.next();}}memend = Runtime.getRuntime().freeMemory();long end = System.currentTimeMillis();LOG.info("STax - (" + nbrRecords + "): - Total memory used: " + (memstart - memend));LOG.info("STax - (" + nbrRecords + "): Time taken in ms: " + (end - start));} finally {xmlr.close();}
}
請注意,這次創建上下文時,我必須指定它用于PersonType對象,并且在調用JAXB編組時,我還必須傳遞所需的返回類類型,并帶有:
JAXBElement<PersonType> pt = unmarshaller.unmarshal(xmlr, PersonType.class);
請注意,我對對象不做任何事情,只是創建它,以通過不引入任何不必要的步驟來使基準保持真實和可能。
JAXB + Woodstox
對于Woodstox,此方法與STax所使用的方法非常相似。 實際上,Woodstox提供了與STax2兼容的API,所以我要做的就是提供正確的工廠,然后……砰! 我讓伍德斯托克斯在掩護下工作。
private void readLargeXmlWithFasterStax(File file, int nbrRecords)throws FactoryConfigurationError, XMLStreamException,FileNotFoundException, JAXBException {// set up a Woodstox readerXMLInputFactory xmlif = XMLInputFactory2.newInstance();XMLStreamReader xmlr = xmlif.createXMLStreamReader(new FileReader(file));JAXBContext ucontext = JAXBContext.newInstance(PersonType.class);Unmarshaller unmarshaller = ucontext.createUnmarshaller();long start = System.currentTimeMillis();long memstart = Runtime.getRuntime().freeMemory();long memend = 0L;try {xmlr.nextTag();xmlr.require(XMLStreamConstants.START_ELEMENT, null, "persons");xmlr.nextTag();while (xmlr.getEventType() == XMLStreamConstants.START_ELEMENT) {JAXBElement<PersonType> pt = unmarshaller.unmarshal(xmlr,PersonType.class);if (xmlr.getEventType() == XMLStreamConstants.CHARACTERS) {xmlr.next();}}memend = Runtime.getRuntime().freeMemory();long end = System.currentTimeMillis();LOG.info("Woodstox - (" + nbrRecords + "): Total memory used: " + (memstart - memend));LOG.info("Woodstox - (" + nbrRecords + "): Time taken in ms: " + (end - start));} finally {xmlr.close();}
}
請注意以下行:
XMLInputFactory xmlif = XMLInputFactory2.newInstance();
我在哪里傳遞STax2 XMLInputFactory。 這使用Woodstox實現。
主循環
一旦文件就位(通過傳遞-Dcreate.xml = true獲得此文件),主文件將執行以下操作:
System.gc();
System.gc();for (int i = 0; i < 10; i++) {main.readLargeFileWithJaxb(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-10000.xml"), 10000);main.readLargeFileWithJaxb(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-100000.xml"), 100000);main.readLargeFileWithJaxb(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-1000000.xml"), 1000000);main.readLargeXmlWithStax(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-10000.xml"), 10000);main.readLargeXmlWithStax(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-100000.xml"), 100000);main.readLargeXmlWithStax(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-1000000.xml"), 1000000);main.readLargeXmlWithFasterStax(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-10000.xml"), 10000);main.readLargeXmlWithFasterStax(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-100000.xml"), 100000);main.readLargeXmlWithFasterStax(new File(OUTPUT_FOLDER + File.separatorChar + "large-person-1000000.xml"), 1000000);
}
它邀請GC運行,盡管我們知道這是由GC線程決定的。 然后,它將每個策略執行10次,以標準化 RAM和CPU消耗。 然后通過十次運行的平均值來收集最終數據。
內存消耗的基準測試結果
以下是一些圖表,這些圖表顯示了在解組10,000 / 100,000 / 1,000,000文件時不同運行環境下的內存消耗。
您可能會注意到,與STax相關的策略的內存消耗通常顯示為負值。 這意味著在解組所有元素之后比在解組循環開始時有更多的空閑內存。 反過來,這表明GC使用STax運行的次數比使用JAXB運行的次數多得多。 如果有人考慮,這是合乎邏輯的。 由于使用STax時,我們不會將所有對象都保留在內存中,因此有更多對象可用于垃圾回收。 在這種特殊情況下,我相信在while循環中創建的PersonType對象符合GC的條件,并進入年輕一代區域,然后被GC回收。 但是,這應該對性能產生最小的影響,因為我們知道從年輕一代空間聲明對象非常有效。
10,000個XML元素的摘要

100,000個XML元素的摘要

1,000,000個XML元素的摘要

處理速度的基準結果
10,000個元素的結果

100,000個元素的結果

1,000,000個元素的結果

結論
在所有三種不同環境下的結果,盡管有所不同,但都告訴我們相同的故事:
- 如果您正在尋找性能(例如XML解組速度),請選擇JAXB
- 如果您正在尋找內存不足的使用(并準備犧牲一些性能速度),請使用STax。
我個人的看法是,我不會選擇Woodstox,但是我會選擇JAXB(如果我需要處理能力并且可以負擔得起RAM)或STax(如果我不需要最高速度并且基礎設施資源不足) )。 這兩種技術都是Java標準,并且是從Java 6開始的JDK的一部分。
資源基準測試器源代碼
- 郵編版本: 下載Large-xml-parser-1.0.0-SNAPSHOT-project
- tar.gz版本: 下載Large-xml-parser-1.0.0-SNAPSHOT-project.tar
- tar.bz2版本: 下載Large-xml-parser-1.0.0-SNAPSHOT-project.tar
標桿可執行文件:
- 郵編版本: 下載Large-xml-parser-1.0.0-SNAPSHOT-bin
- tar.gz版本: 下載Large-xml-parser-1.0.0-SNAPSHOT-bin.tar
- tar.bz2版本: 下載Large-xml-parser-1.0.0-SNAPSHOT-bin.tar
數據文件:
- Ubuntu 64位VM運行環境: 下載Stax-vs-jaxb-ubuntu-64-vm
- Ubuntu 32位運行環境: 下載Stax-vs-jaxb-ubuntu-32位
- Windows 7 Ultimate運行環境: 下載Stax-vs-jaxb-windows7
參考: Java中的XML解組基準測試: Marco Tedone博客博客上的JCG合作伙伴 Marco Tedone的JAXB vs STax vs Woodstox 。
翻譯自: https://www.javacodegeeks.com/2012/05/xml-unmarshalling-benchmark-jaxb-vs.html