上周末,我开始考虑如何以一种资源友好的方式处理大量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