测试过大输入的代码

在编写单元测试时,我们主要关注业务的正确性。 我们将竭尽所能,开开心心地走在最前沿。 我们有时会进行微基准测试并衡量吞吐量。 但是经常遗漏的一个方面是当输入过大时我们的代码如何表现? 我们测试了如何处理正常的输入文件,格式错误的文件,空文件,丢失的文件……但是对于超大的输入文件呢?

让我们从一个真实的用例开始。 您已获得将GPX ( GPS交换格式 ,基本上为XML)实现为JSON转换的任务。 我选择GPX的原因并不特别,它只是您可能遇到的另一种XML格式,例如在用GPS接收器记录远足或骑自行车时。 我还认为使用XML中的一些标准而不是另一个“人员数据库”会很好。 GPX文件中有数百个平面<wpt/>条目,每个条目代表时空的一点:

<gpx><wpt lat="42.438878" lon="-71.119277"><ele>44.586548</ele><time>2001-11-28T21:05:28Z</time><name>5066</name><desc><![CDATA[5066]]></desc><sym>Crossing</sym><type><![CDATA[Crossing]]></type></wpt><wpt lat="42.439227" lon="-71.119689"><ele>57.607200</ele><time>2001-06-02T03:26:55Z</time><name>5067</name><desc><![CDATA[5067]]></desc><sym>Dot</sym><type><![CDATA[Intersection]]></type></wpt><!-- ...more... -->
</gpx>

完整示例: www.topografix.com/fells_loop.gpx 。 我们的任务是提取每个单独的<wpt/>元素,丢弃没有latlon属性的元素,并以以下格式存储回JSON:

[{"lat": 42.438878,"lon": -71.119277},{"lat": 42.439227,"lon": -71.119689}...more...
]

这很容易! 首先,我从使用JDK和GPX 1.0 XSD架构的 xjc实用程序生成JAXB类开始。 请注意,GPX 1.1是撰写本文时的最新版本,但是我得到的示例使用1.0。 对于JSON编组,我使用了Jackson 。 完整,可运行且经过测试的程序如下所示:

import org.apache.commons.io.FileUtils;
import org.codehaus.jackson.map.ObjectMapper;
import javax.xml.bind.JAXBException;public class GpxTransformation {private final ObjectMapper jsonMapper = new ObjectMapper();private final JAXBContext jaxbContext;public GpxTransformation() throws JAXBException {jaxbContext = JAXBContext.newInstance("com.topografix.gpx._1._0");}public void transform(File inputFile, File outputFile) throws JAXBException, IOException {final List<Gpx.Wpt> waypoints = loadWaypoints(inputFile);final List<LatLong> coordinates = toCoordinates(waypoints);dumpJson(coordinates, outputFile);}private List<Gpx.Wpt> loadWaypoints(File inputFile) throws JAXBException, IOException {String xmlContents = FileUtils.readFileToString(inputFile, UTF_8);final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();final Gpx gpx = (Gpx) unmarshaller.unmarshal(new StringReader(xmlContents));return gpx.getWpt();}private static List<LatLong> toCoordinates(List<Gpx.Wpt> waypoints) {return waypoints.stream().filter(wpt -> wpt.getLat() != null).filter(wpt -> wpt.getLon() != null).map(LatLong::new).collect(toList());}private void dumpJson(List<LatLong> coordinates, File outputFile) throws IOException {final String resultJson = jsonMapper.writeValueAsString(coordinates);FileUtils.writeStringToFile(outputFile, resultJson);}}class LatLong {private final double lat;private final double lon;LatLong(Gpx.Wpt waypoint) {this.lat = waypoint.getLat().doubleValue();this.lon = waypoint.getLon().doubleValue();}public double getLat() { return lat; }public double getLon() { return lon; }
}

看起来还不错,尽管我故意留下了一些陷阱。 我们加载GPX XML文件,将航点提取到List ,然后将该列表转换为轻量级的LatLong对象,首先过滤掉损坏的航点。 最后,我们将List<LatLong>转储到磁盘。 然而,有一天,极其漫长的自行车骑行使我们的系统因OutOfMemoryError崩溃。 你可知道发生了什么? 上传到我们的应用程序的GPX文件很大,比我们预期的要大得多。 现在再看一下上面的实现,并计算在多少地方分配了必要的内存?

但是,如果您想立即进行重构,请就在此处停止! 我们想练习TDD,对吗? 我们要在代码中限制WTF /分钟因素吗? 我有一个理论,许多“ WTF”不是由粗心和缺乏经验的程序员引起的。 通常是由于周五晚些时候的生产问题,完全出乎意料的输入和不可预料的副作用。 代码获得了越来越多的变通办法,难以理解的重构,以及比人们预期的更复杂的逻辑。 有时不希望有错误的代码,但是由于我们早已忘记了这种情况,所以需要这样做。 因此,如果有一天您看到不可能发生的null检查或可能已被库替换的手写代码,请考虑上下文。 话虽这么说,让我们从编写测试开始以证明我们将来的重构是必要的。 如果有一天有人“固定”我们的代码,并假设“这位愚蠢的程序员”在没有充分理由的情况下使事情复杂化,那么自动化测试将准确地说明原因

我们的测试将仅尝试转换疯狂的大输入文件。 但是在开始之前,我们必须对原始实现进行一些重构,以使其能够容纳InputStreamOutputStream而不是输入和输出File -没有理由将我们的实现仅限于文件系统:

步骤0a:使其可测试

import org.apache.commons.io.IOUtils;public class GpxTransformation {//...public void transform(File inputFile, File outputFile) throws JAXBException, IOException {try (InputStream input =new BufferedInputStream(new FileInputStream(inputFile));OutputStream output =new BufferedOutputStream(new FileOutputStream(outputFile))) {transform(input, output);}}public void transform(InputStream input, OutputStream output) throws JAXBException, IOException {final List<Gpx.Wpt> waypoints = loadWaypoints(input);final List<LatLong> coordinates = toCoordinates(waypoints);dumpJson(coordinates, output);}private List<Gpx.Wpt> loadWaypoints(InputStream input) throws JAXBException, IOException {String xmlContents = IOUtils.toString(input, UTF_8);final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();final Gpx gpx = (Gpx) unmarshaller.unmarshal(new StringReader(xmlContents));return gpx.getWpt();}//...private void dumpJson(List<LatLong> coordinates, OutputStream output) throws IOException {final String resultJson = jsonMapper.writeValueAsString(coordinates);output.write(resultJson.getBytes(UTF_8));}}

步骤0b:编写输入(压力)测试

使用先前开发的 repeat(byte[] sample, int times)实用工具将从头开始生成输入。 我们基本上将重复相同的<wpt/>项数百万次,并用GPX页眉和页脚将其包装起来,以便其格式正确。 通常,我会考虑将样本放在src/test/resources ,但是我希望此代码能够自我包含。 注意,我们既不在乎实际的输入,也不在乎输出。 这已经过测试。 如果转换成功(如果需要,我们可以添加一些超时),那就可以了。 如果它因任何异常而失败,很可能是OutOfMemoryError ,则是测试失败(错误):

import org.apache.commons.io.FileUtils
import org.apache.commons.io.output.NullOutputStream
import spock.lang.Specification
import spock.lang.Unrollimport static org.apache.commons.io.FileUtils.ONE_GB
import static org.apache.commons.io.FileUtils.ONE_KB
import static org.apache.commons.io.FileUtils.ONE_MB@Unroll
class LargeInputSpec extends Specification {final GpxTransformation transformation = new GpxTransformation()final byte[] header = """<?xml version="1.0"?><gpxversion="1.0"creator="ExpertGPS 1.1 - http://www.topografix.com"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.topografix.com/GPX/1/0"xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd"><time>2002-02-27T17:18:33Z</time>""".getBytes(UTF_8)final byte[] gpxSample = """<wpt lat="42.438878" lon="-71.119277"><ele>44.586548</ele><time>2001-11-28T21:05:28Z</time><name>5066</name><desc><![CDATA[5066]]></desc><sym>Crossing</sym><type><![CDATA[Crossing]]></type></wpt>""".getBytes(UTF_8)final byte[] footer = """</gpx>""".getBytes(UTF_8)def "Should not fail with OOM for input of size #readableBytes"() {given:int repeats = size / gpxSample.lengthInputStream xml = withHeaderAndFooter(RepeatedInputStream.repeat(gpxSample, repeats))expect:transformation.transform(xml, new NullOutputStream())where:size << [ONE_KB, ONE_MB, 10 * ONE_MB, 100 * ONE_MB, ONE_GB, 8 * ONE_GB, 32 * ONE_GB]readableBytes = FileUtils.byteCountToDisplaySize(size)}private InputStream withHeaderAndFooter(InputStream samples) {InputStream withHeader = new SequenceInputStream(new ByteArrayInputStream(header), samples)return new SequenceInputStream(withHeader, new ByteArrayInputStream(footer))}
}

实际上,这里有7个测试,运行GPX到JSON转换以输入大小:1 KiB,1 MiB,10 MiB,100 MiB,1 GiB,8 GiB和32 GiB。 我在JDK 8u11x64上使用以下选项运行这些测试: -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xmx1g 。 1 GiB的内存很多,但是显然不能容纳整个输入文件在内存中:

1b

当小测试通过时,高于1 GiB的输入将快速失败。

步骤1:避免将整个文件保留在

堆栈跟踪揭示了问题所在:

java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3326)at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:569)at java.lang.StringBuilder.append(StringBuilder.java:190)at org.apache.commons.io.output.StringBuilderWriter.write(StringBuilderWriter.java:138)at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2002)at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1980)at org.apache.commons.io.IOUtils.copy(IOUtils.java:1957)at org.apache.commons.io.IOUtils.copy(IOUtils.java:1907)at org.apache.commons.io.IOUtils.toString(IOUtils.java:778)at com.nurkiewicz.gpx.GpxTransformation.loadWaypoints(GpxTransformation.java:56)at com.nurkiewicz.gpx.GpxTransformation.transform(GpxTransformation.java:50)

loadWaypoints急切地将input GPX文件加载到String (请参阅: IOUtils.toString(input, UTF_8) ),以便稍后对其进行解析。 这有点愚蠢,尤其是因为JAXB Unmarshaller可以轻松地直接读取InputStream 。 让我们修复它:

private List<Gpx.Wpt> loadWaypoints(InputStream input) throws JAXBException, IOException {final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();final Gpx gpx = (Gpx) unmarshaller.unmarshal(input);return gpx.getWpt();
}private void dumpJson(List<LatLong> coordinates, OutputStream output) throws IOException {jsonMapper.writeValue(output, coordinates);
}

同样,我们修复了dumpJson因为它首先将JSON转储到String ,然后将该String复制到OutputStream 。 结果略好一些,但再次出现1 GiB失败,这一次是进入Full GC的无限死亡循环,最后抛出:

java.lang.OutOfMemoryError: Java heap spaceat com.sun.xml.internal.bind.v2.runtime.unmarshaller.LeafPropertyLoader.text(LeafPropertyLoader.java:50)at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.text(UnmarshallingContext.java:527)at com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.processText(SAXConnector.java:208)at com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.endElement(SAXConnector.java:171)at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:609)[...snap...]at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:649)at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:243)at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:214)at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:157)at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:204)at com.nurkiewicz.gpx.GpxTransformation.loadWaypoints(GpxTransformation.java:54)at com.nurkiewicz.gpx.GpxTransformation.transform(GpxTransformation.java:47)

步骤2 :(以较差的方式)用StAX替换JAXB

我们可以怀疑,现在的主要问题是使用JAXB进行XML解析,JAXB总是将整个XML文件映射到Java对象中。 很难想象为什么将1 GiB文件转换为对象图会失败。 我们想以某种方式更好地控制读取XML并将其分块使用。 传统上在这种情况下使用SAX,但是SAX API中的推式编程模型非常不便。 SAX使用回调机制,该机制具有很高的侵入性,并且不易读。 StAX(用于XML的流API)在更高级别上工作,公开了拉模型。 这意味着客户代码决定何时以及消耗多少输入。 这使我们可以更好地控制输入,并具有更大的灵活性。 为了使您熟悉该API,以下代码几乎等同于loadWaypoints() ,但是我跳过了<wpt/>属性,这些属性以后不再需要:

private List<Gpx.Wpt> loadWaypoints(InputStream input) throws JAXBException, IOException, XMLStreamException {final XMLInputFactory factory = XMLInputFactory.newInstance();final XMLStreamReader reader = factory.createXMLStreamReader(input);final List<Gpx.Wpt> waypoints = new ArrayList<>();while (reader.hasNext()) {switch (reader.next()) {case XMLStreamConstants.START_ELEMENT:if (reader.getLocalName().equals("wpt")) {waypoints.add(parseWaypoint(reader));}break;}}return waypoints;
}private Gpx.Wpt parseWaypoint(XMLStreamReader reader) {final Gpx.Wpt wpt = new Gpx.Wpt();final String lat = reader.getAttributeValue("", "lat");if (lat != null) {wpt.setLat(new BigDecimal(lat));}final String lon = reader.getAttributeValue("", "lon");if (lon != null) {wpt.setLon(new BigDecimal(lon));}return wpt;
}

看看我们如何明确地向XMLStreamReader请求更多数据? 然而事实是,我们正在使用更多的低级别的API( 大量的代码),并不意味着它必须是更好的,如果使用不当。 我们一直在构建庞大的waypoints列表,因此再次看到OutOfMemoryError也就不足为奇了:

java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3204)at java.util.Arrays.copyOf(Arrays.java:3175)at java.util.ArrayList.grow(ArrayList.java:246)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:220)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:212)at java.util.ArrayList.add(ArrayList.java:443)at com.nurkiewicz.gpx.GpxTransformation.loadWaypoints(GpxTransformation.java:65)at com.nurkiewicz.gpx.GpxTransformation.transform(GpxTransformation.java:52)

正是我们所期望的。 好消息是,1个吉布测试通过(1个吉布堆),所以我们有几分在正确的方向前进。 但是由于GC过多,需要1分钟才能完成。

步骤3:正确实施StAX

注意,在前面的示例中使用StAX的实现与SAX一样好。 但是,我选择StAX的原因是我们现在可以将XML文件转换为Iterator<Gpx.Wpt> 。 该迭代器仅在被询问时才懒散地使用XML文件。 以后我们也可以延迟使用该迭代器,这意味着我们不再将整个文件保留在内存中。 迭代器虽然笨拙,但比直接使用XML或使用SAX回调要好得多:

import com.google.common.collect.AbstractIterator;private Iterator<Gpx.Wpt> loadWaypoints(InputStream input) throws JAXBException, IOException, XMLStreamException {final XMLInputFactory factory = XMLInputFactory.newInstance();final XMLStreamReader reader = factory.createXMLStreamReader(input);return new AbstractIterator<Gpx.Wpt>() {@Overrideprotected Gpx.Wpt computeNext() {try {return tryPullNextWaypoint();} catch (XMLStreamException e) {throw Throwables.propagate(e);}}private Gpx.Wpt tryPullNextWaypoint() throws XMLStreamException {while (reader.hasNext()) {int event = reader.next();switch (event) {case XMLStreamConstants.START_ELEMENT:if (reader.getLocalName().equals("wpt")) {return parseWaypoint(reader);}break;case XMLStreamConstants.END_ELEMENT:if (reader.getLocalName().equals("gpx")) {return endOfData();}break;}}throw new IllegalStateException("XML file didn't finish with </gpx> element, malformed?");}};
}

这变得越来越复杂! 我正在使用来自Guava的AbstractIterator来处理乏味的hasNext()状态。 每次有人尝试从迭代器中提取下一个Gpx.Wpt项(或调用hasNext() )时,我们都会消耗一点XML,足以返回一个条目。 如果XMLStreamReader遇到XML的结尾( </gpx>标记),我们将通过返回endOfData()通知迭代器结束。 这是一种非常方便的模式,其中XML被懒惰地读取并通过方便的迭代器提供服务。 仅此实现就消耗很少的,恒定的内存量。 但是,我们将API从List<Gpx.Wpt>更改为Iterator<Gpx.Wpt> ,这将强制更改其余实现:

private static List<LatLong> toCoordinates(Iterator<Gpx.Wpt> waypoints) {final Spliterator<Gpx.Wpt> spliterator =Spliterators.spliteratorUnknownSize(waypoints, Spliterator.ORDERED);return StreamSupport.stream(spliterator, false).filter(wpt -> wpt.getLat() != null).filter(wpt -> wpt.getLon() != null).map(LatLong::new).collect(toList());
}

toCoordinates()以前接受List<Gpx.Wpt> 。 迭代器无法直接转换为Stream ,因此我们需要通过Spliterator笨拙的转换。 你认为结束了吗? ! GiB测试通过得更快一些,但更多苛刻的测试却像以前一样失败了:

java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3175)at java.util.ArrayList.grow(ArrayList.java:246)at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:220)at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:212)at java.util.ArrayList.add(ArrayList.java:443)at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)at java.util.Iterator.forEachRemaining(Iterator.java:116)at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)at com.nurkiewicz.gpx.GpxTransformation.toCoordinates(GpxTransformation.java:118)at com.nurkiewicz.gpx.GpxTransformation.transform(GpxTransformation.java:58)at com.nurkiewicz.LargeInputSpec.Should not fail with OOM for input of size #readableBytes(LargeInputSpec.groovy:49)

请记住,并非总是从实际上消耗大量内存的地方抛出OutOfMemoryError 。 幸运的是,这次并非如此。 仔细查看底部: collect(toList())

步骤4:避免流和收集器

这真令人失望。 溪流和收集器的设计完全是为了支持懒惰。 但是,实际上不可能有效地实现从流到迭代器的收集器(另请参见: Java 8中编写自定义收集器以及分组,采样和批处理–自定义收集器的简介 ),这是一个很大的设计缺陷。 因此,我们必须完全忘记流,并一直使用简单的迭代器。 迭代器不是很优雅,但是可以逐项消耗输入,可以完全控制内存消耗。 我们需要一种方法来filter()输入迭代器,丢弃损坏的项并将map()条目map()到另一个表示形式。 番石榴再次提供了一些方便的实用程序,完全替换了stream()

private static Iterator<LatLong> toCoordinates(Iterator<Gpx.Wpt> waypoints) {final Iterator<Gpx.Wpt> filtered = Iterators.filter(waypoints, wpt -> wpt.getLat() != null && wpt.getLon() != null);return Iterators.transform(filtered, LatLong::new);
}

Iterator<Gpx.Wpt>在, Iterator<LatLong>出。 没有进行任何处理,几乎没有触及XML文件,几乎没有内存消耗。 我们很幸运,杰克逊接受迭代器并透明地读取它们,从而迭代生成JSON。 因此,存储器消耗也保持较低。 猜猜是什么,我们做到了!

6b

内存消耗低且稳定,我认为我们可以放心地假设它是恒定的。 我们的代码以大约40 MiB / s的速度处理,因此处理32 GiB大约需要14分钟,不要感到惊讶。 哦,我是否提到我使用-Xmx32M运行了最后一个测试? 没错,使用少千倍的内存即可成功处理32 GiB,而不会造成任何性能损失。 与初始实施相比,减少了3000倍。 事实上,最后一个使用迭代器的解决方案甚至能够处理无限的XML流。 这实际上不只是理论上的情况,想象一下某种流API会产生永无止境的消息流…

最终实施

这是我们完整的代码:

package com.nurkiewicz.gpx;import com.google.common.base.Throwables;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterators;
import com.topografix.gpx._1._0.Gpx;
import org.codehaus.jackson.map.ObjectMapper;import javax.xml.bind.JAXBException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.Iterator;public class GpxTransformation {private static final ObjectMapper jsonMapper = new ObjectMapper();public void transform(File inputFile, File outputFile) throws JAXBException, IOException, XMLStreamException {try (InputStream input =new BufferedInputStream(new FileInputStream(inputFile));OutputStream output =new BufferedOutputStream(new FileOutputStream(outputFile))) {transform(input, output);}}public void transform(InputStream input, OutputStream output) throws JAXBException, IOException, XMLStreamException {final Iterator<Gpx.Wpt> waypoints = loadWaypoints(input);final Iterator<LatLong> coordinates = toCoordinates(waypoints);dumpJson(coordinates, output);}private Iterator<Gpx.Wpt> loadWaypoints(InputStream input) throws JAXBException, IOException, XMLStreamException {final XMLInputFactory factory = XMLInputFactory.newInstance();final XMLStreamReader reader = factory.createXMLStreamReader(input);return new AbstractIterator<Gpx.Wpt>() {@Overrideprotected Gpx.Wpt computeNext() {try {return tryPullNextWaypoint();} catch (XMLStreamException e) {throw Throwables.propagate(e);}}private Gpx.Wpt tryPullNextWaypoint() throws XMLStreamException {while (reader.hasNext()) {int event = reader.next();switch (event) {case XMLStreamConstants.START_ELEMENT:if (reader.getLocalName().equals("wpt")) {return parseWaypoint(reader);}break;case XMLStreamConstants.END_ELEMENT:if (reader.getLocalName().equals("gpx")) {return endOfData();}break;}}throw new IllegalStateException("XML file didn't finish with </gpx> element, malformed?");}};}private Gpx.Wpt parseWaypoint(XMLStreamReader reader) {final Gpx.Wpt wpt = new Gpx.Wpt();final String lat = reader.getAttributeValue("", "lat");if (lat != null) {wpt.setLat(new BigDecimal(lat));}final String lon = reader.getAttributeValue("", "lon");if (lon != null) {wpt.setLon(new BigDecimal(lon));}return wpt;}private static Iterator<LatLong> toCoordinates(Iterator<Gpx.Wpt> waypoints) {final Iterator<Gpx.Wpt> filtered = Iterators.filter(waypoints, wpt ->wpt.getLat() != null &&wpt.getLon() != null);return Iterators.transform(filtered, LatLong::new);}private void dumpJson(Iterator<LatLong> coordinates, OutputStream output) throws IOException {jsonMapper.writeValue(output, coordinates);}}

摘要(TL; DR)

如果您没有足够的耐心执行所有步骤,则可以参考以下三点:

  1. 您的首要目标是简单 。 最初的JAXB实现非常好(只需进行少量修改),如果您的代码不必处理大量输入,则应保持这种状态。
  2. 针对巨大的输入 (例如,使用生成的InputStream )生成千兆字节的输入,以测试您的代码 。 巨大的数据集是边缘情况的另一个例子。 一次不要手动测试。 一不小心的更改或“改进”可能会破坏您的性能。
  3. 优化不是编写不良代码的借口 。 请注意,我们的实现仍然是可组合的,并且易于遵循。 如果我们通过SAX并简单地内联SAX回调中的所有逻辑,则可维护性将受到很大影响。

翻译自: https://www.javacodegeeks.com/2014/08/testing-code-for-excessively-large-inputs.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366209.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

资源不在java项目和构建路径上_编译单元不在Java项目的构建路径上-Maven

今天,我已经在日食中导入了一个Maven项目.当我尝试自动建议时,当我添加一些代码时,它提示我“编译单元不在Java项目的构建路径上”.我没有看到解决此问题的方法,但是都没有解决.这个应该做什么&#xff1f;xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://m…

Django 第八课 2.【MySQL相关软件 mysqlclient Navicat Premium】

MySQL 驱动程序安装&#xff1a; 我们使用 Django 来操作 MySQL&#xff0c;实际上底层还是通过 Python来操作的。因此我们想要用 Django 来操作 MySQL&#xff0c;首先还是需要安装一个驱动程序。在python3中&#xff0c;驱动程序有多种选择。比如pymysql 以及mysqlclient等。…

查看串口的驱动信息

1、查看串口的驱动信息 2、驱动安装文件&#xff1a;http://www.wch.cn/download/CH341SER_EXE.html 3、参考驱动资料:https://blog.csdn.net/jazzsoldier/article/details/70169732 转载于:https://www.cnblogs.com/qy1234/p/10146303.html

java中怎么判断一段代码时线程安全还是非线程安全_Java 中的多线程你只要看这一篇就够了...

引如果对什么是线程、什么是进程仍存有疑惑&#xff0c;请先Google之&#xff0c;因为这两个概念不在本文的范围之内。用多线程只有一个目的&#xff0c;那就是更好的利用cpu的资源&#xff0c;因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对&#xff0c;因…

实现 通过数据库里一个字段值相等 则把 他合为一条数据

需求&#xff1a; 把红色合为一条数据&#xff0c;绿色合为一条数据 结果&#xff1a; 不多说直接上代码......................................................... // 先查所有数据 const goods await service.goods.findAllGoodsWithParams({where: params,offset: ctx.p…

float与double类型参数区别_8大基本数据类型及包装类,不知道这些点别说自己是大佬...

一般地&#xff0c;当需要使用数字的时候&#xff0c;我们通常使用内置数据类型&#xff0c;如&#xff1a;byte、int、long、double 等。然而&#xff0c;在实际开发过程中&#xff0c;我们经常会遇到需要使用对象&#xff0c;而不是内置数据类型的情形。为了解决这个问题&…

9-客户端集成IdentityServer

1-创建客户端的webapi项目 E:\coding\netcore\IdentityServerSample>dotnet new webapi --name IdentityCredentialApi 2-在需要启用授权的方法上增加Authorize标签 3-使用nuget工具安装 IdentityServer4.AccessTokenValidation 4-启用客户端授权&#xff0c; 需要配置连接…

js替换数组中字符串实例

这个是替换数组中的一个对象字符串&#xff1b; 直接上代码&#xff1a; 1 var aaa[ 2 {"name":"张珊","sex":"man"}, 3 {"name":"李斯","sex":"woman"}, 4 …

ie浏览器升级_微软呼吁用户停用IE浏览器 2020年将不再更新升级

驱动中国2019年2月10日消息 IE浏览器作为美国微软公司推出的一款网页浏览器成为许多人接触互联网初选&#xff0c;深受不少用户喜欢但这款流畅多年的浏览器正式遭弃用&#xff01;今日微软网络安全专家在在官方Winhows IT Pro上发帖呼吁用户停止使用IE浏览器&#xff01;微软网…

CentOS安装zookeeper

第一步&#xff0c;先去Apache下载安装包 http://mirror.bit.edu.cn/apache/zookeeper/ 第二步&#xff0c;上传到服务器并解压 tar -zxvf zookeeper-3.4.6 第三步&#xff0c;新建一个文件夹 #先改个名 mv zookeeper-3.4.6 zookeeper cd zookeeper mkdir data 第四步&#xff…

rdlc报表 矩形高固定_固定资产条码管理系统特点分析

固定资产条码管理系统的特点都有什么&#xff1f;固定资产是维持企业稳定健康发展的重要物质保障&#xff0c;若想最大化发挥固定资产的效能作用&#xff0c;满足生产经营管理的需求&#xff0c;提高应对企业内外部经营环境变化的能力&#xff0c;企业管理者就必须及时了解固定…

bzoj 2007 [Noi2010]海拔——最小割转最短路

题目&#xff1a;https://www.lydsy.com/JudgeOnline/problem.php?id2007 一个点的高度一定不是0就是1。答案一定形如一个左上角的连通块全是0的点、一个右下角的连通块全是1的点。 注意从东到西还有从南到北的边也有用&#xff01;因为不一定是一个阶梯形的&#xff0c;还可以…

《黑客攻防技术宝典Web实战篇@第2版》读书笔记1:了解Web应用程序

读书笔记第一部分对应原书的第一章&#xff0c;主要介绍了Web应用程序的发展&#xff0c;功能&#xff0c;安全状况。 Web应用程序的发展历程 早期的万维网仅由Web站点构成&#xff0c;只是包含静态文档的信息库&#xff0c;随后人们发明了Web浏览器用来检索和显示那些文档&am…

电脑入门完全自学手册_3DMAX零基础入门到精通的学习路线和教程

没有美术基础可以学建模吗&#xff1f;毋庸置疑&#xff0c;只要肯学都是可以的&#xff01;那么我们今天来说说零基础学习建模要着重注意什么。最高效最快速的入门学习方式&#xff1a;看知识兔视频课程&#xff0c;跟着知识兔老师操作&#xff0c;听知识兔老师讲解&#xff0…

Python-UiAutomator2实现Android自动化测试

本帖转自搜狗测试公众号 【一、前言】 基于Python-UiAutomator2实现Android自动化测试&#xff0c;小编在Android应用的自动化性能测试中进行了实践。本篇将简单介绍python中使用adb、aapt命令的方法以及Python-UiAutomator2使用&#xff0c;后续文章将对环境搭建以及自动化性能…

硬件信息修改大师_零成本学习之单片机硬件开发(1)

从今天开始给大家逐步讲解单片机编程&#xff0c;有兴趣的小伙伴们&#xff0c;可以关注本课程&#xff0c;完全免费本人从事从2004年大学毕业开始从事开发工作&#xff0c;先后做过单片机开发、ucOSII开发、手机开发、android APP开发、PHP开发、微信小程序开发&#xff0c;目…

ionic3 自动创建启动背景splash以及图标icon

在新建的项目文件夹下的 resources下就是我们放置图标以及启动背景图片的位置了。 如果现在我们想生成自己的图片的启动背景以及图片&#xff0c;我们需要把resources下的 icon.png 以及splash.png 替换为我们的图片。 icon 图片最好为1024x1024像素 splash 图片最好为2732x2…

Spring容器的简单实现(IOC原理)

引言&#xff1a;容器是什么&#xff1f;什么是容器&#xff1f;Spring容器又是啥东西&#xff1f;我给Spring容器一个对象名字&#xff0c;为啥能给我创建一个对象呢&#xff1f; 一、容器是装东西的&#xff0c;就像你家的水缸&#xff0c;你吃饭的碗等等。 java中能作为容器…

epic怎么添加本地游戏_游戏日报:原神公测无法上架多家安卓渠道;Epic投资布局游戏UGC平台...

// 热点标签/// NOCITCE华为|小米|OPPO|米哈游|腾讯|莉莉丝B站|阅文集团|Epic|万国觉醒|灰烬战线FGO|怪物弹珠|勇者斗恶龙|金币大师阴阳师|和平精英|王者荣耀|三国志战略版1.华为、小米、OPPO等多家安卓渠道的《原神》版主发布公告称&#xff0c;因开发者要求/未与米哈游达成一…

php 出错处理,PHP 错误处理机制

在日常的项目开发过程中&#xff0c;总是会出现一些我们意想不到的异常错误&#xff0c;如果我们对此没有进行相对完善的处理&#xff0c;那么程序看上去也很不专业&#xff0c;也很可能就会成为别人攻击系统的有效信息&#xff1b;有些错误异常会终止脚本执行&#xff0c;这个…