Skip to content

Commit 61b4f79

Browse files
authored
Fast parser, generator support (#314)
1 parent b6c49d4 commit 61b4f79

13 files changed

+164
-26
lines changed

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/CsvGenerator.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,8 @@ public CsvGenerator(IOContext ctxt, int jsonFeatures, int csvFeatures,
245245
_ioContext = ctxt;
246246
_formatFeatures = csvFeatures;
247247
_schema = schema;
248-
_writer = new CsvEncoder(ctxt, csvFeatures, out, schema);
248+
boolean useFastDoubleWriter = StreamWriteFeature.USE_FAST_DOUBLE_WRITER.enabledIn(jsonFeatures);
249+
_writer = new CsvEncoder(ctxt, csvFeatures, out, schema, useFastDoubleWriter);
249250
_writeContext = null; // just to make sure it won't be used
250251
_tokenWriteContext = SimpleTokenWriteContext.createRootContext(null);
251252
_writer.setOutputEscapes(CsvCharacterEscapes.fromCsvFeatures(csvFeatures).getEscapeCodesForAscii());

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/impl/CsvDecoder.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1228,7 +1228,7 @@ private final void _parseSlowFloatValue(boolean exactNumber)
12281228
_numTypesValid = NR_BIGDECIMAL;
12291229
} else {
12301230
// Otherwise double has to do
1231-
_numberDouble = _textBuffer.contentsAsDouble();
1231+
_numberDouble = _textBuffer.contentsAsDouble(_owner.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
12321232
_numTypesValid = NR_DOUBLE;
12331233
}
12341234
} catch (NumberFormatException nex) {

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/impl/CsvEncoder.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.fasterxml.jackson.dataformat.csv.impl;
22

3+
import com.fasterxml.jackson.core.JsonGenerator;
34
import com.fasterxml.jackson.core.io.CharTypes;
45
import com.fasterxml.jackson.core.io.IOContext;
56
import com.fasterxml.jackson.dataformat.csv.CsvGenerator;
@@ -116,6 +117,11 @@ public class CsvEncoder
116117
*/
117118
protected boolean _cfgEscapeControlCharWithEscapeChar;
118119

120+
/**
121+
* @since 2.14
122+
*/
123+
protected boolean _cfgUseFastDoubleWriter;
124+
119125
protected final char _cfgQuoteCharEscapeChar;
120126

121127
/**
@@ -193,10 +199,18 @@ public class CsvEncoder
193199
/**********************************************************
194200
*/
195201

202+
203+
@Deprecated //since 2.14
196204
public CsvEncoder(IOContext ctxt, int csvFeatures, Writer out, CsvSchema schema)
205+
{
206+
this(ctxt, csvFeatures, out, schema, false);
207+
}
208+
209+
public CsvEncoder(IOContext ctxt, int csvFeatures, Writer out, CsvSchema schema, boolean useFastDoubleWriter)
197210
{
198211
_ioContext = ctxt;
199212
_csvFeatures = csvFeatures;
213+
_cfgUseFastDoubleWriter = useFastDoubleWriter;
200214
_cfgOptimalQuoting = CsvGenerator.Feature.STRICT_CHECK_FOR_QUOTING.enabledIn(csvFeatures);
201215
_cfgIncludeMissingTail = !CsvGenerator.Feature.OMIT_MISSING_TAIL_COLUMNS.enabledIn(_csvFeatures);
202216
_cfgAlwaysQuoteStrings = CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS.enabledIn(csvFeatures);
@@ -235,6 +249,7 @@ public CsvEncoder(CsvEncoder base, CsvSchema newSchema)
235249
{
236250
_ioContext = base._ioContext;
237251
_csvFeatures = base._csvFeatures;
252+
_cfgUseFastDoubleWriter = base._cfgUseFastDoubleWriter;
238253
_cfgOptimalQuoting = base._cfgOptimalQuoting;
239254
_cfgIncludeMissingTail = base._cfgIncludeMissingTail;
240255
_cfgAlwaysQuoteStrings = base._cfgAlwaysQuoteStrings;
@@ -586,7 +601,7 @@ protected void appendValue(long value) throws IOException
586601

587602
protected void appendValue(float value) throws IOException
588603
{
589-
String str = NumberOutput.toString(value);
604+
String str = NumberOutput.toString(value, _cfgUseFastDoubleWriter);
590605
final int len = str.length();
591606
if ((_outputTail + len) >= _outputEnd) { // >= to include possible comma too
592607
_flushBuffer();
@@ -599,7 +614,7 @@ protected void appendValue(float value) throws IOException
599614

600615
protected void appendValue(double value) throws IOException
601616
{
602-
String str = NumberOutput.toString(value);
617+
String str = NumberOutput.toString(value, _cfgUseFastDoubleWriter);
603618
final int len = str.length();
604619
if ((_outputTail + len) >= _outputEnd) { // >= to include possible comma too
605620
_flushBuffer();

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/impl/NumberInput.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,25 @@ public final static boolean inLongRange(char[] digitChars, int offset, int len,
9494
return true;
9595
}
9696

97-
public final static double parseDouble(String numStr) throws NumberFormatException
98-
{
99-
return Double.parseDouble(numStr);
97+
/**
98+
* @param s a string representing a number to parse
99+
* @return closest matching double
100+
* @throws NumberFormatException if string cannot be represented by a double where useFastParser=false
101+
* @deprecated use {@link #parseDouble(String, boolean)}
102+
*/
103+
@Deprecated //since 2.14
104+
public static double parseDouble(final String s) throws NumberFormatException {
105+
return parseDouble(s, false);
106+
}
107+
108+
/**
109+
* @param s a string representing a number to parse
110+
* @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser}
111+
* @return closest matching double
112+
* @throws NumberFormatException if string cannot be represented by a double
113+
* @since v2.14
114+
*/
115+
public static double parseDouble(final String s, final boolean useFastParser) throws NumberFormatException {
116+
return com.fasterxml.jackson.core.io.NumberInput.parseDouble(s, useFastParser);
100117
}
101118
}

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/impl/NumberOutput.java

+32-6
Original file line numberDiff line numberDiff line change
@@ -220,14 +220,40 @@ public static String toString(long value)
220220
}
221221
*/
222222

223-
public static String toString(double value)
224-
{
225-
return Double.toString(value);
223+
/**
224+
* @param v double
225+
* @return double as a string
226+
*/
227+
public static String toString(final double v) {
228+
return toString(v, false);
226229
}
227230

228-
public static String toString(float value)
229-
{
230-
return Float.toString(value);
231+
/**
232+
* @param v double
233+
* @param useFastWriter whether to use Schubfach algorithm to write output (default false)
234+
* @return double as a string
235+
* @since 2.14
236+
*/
237+
public static String toString(final double v, final boolean useFastWriter) {
238+
return com.fasterxml.jackson.core.io.NumberOutput.toString(v, useFastWriter);
239+
}
240+
241+
/**
242+
* @param v float
243+
* @return float as a string
244+
*/
245+
public static String toString(final float v) {
246+
return toString(v, false);
247+
}
248+
249+
/**
250+
* @param v float
251+
* @param useFastWriter whether to use Schubfach algorithm to write output (default false)
252+
* @return float as a string
253+
* @since 2.14
254+
*/
255+
public static String toString(final float v, final boolean useFastWriter) {
256+
return com.fasterxml.jackson.core.io.NumberOutput.toString(v, useFastWriter);
231257
}
232258

233259
/*

csv/src/main/java/com/fasterxml/jackson/dataformat/csv/impl/TextBuffer.java

+17-3
Original file line numberDiff line numberDiff line change
@@ -312,13 +312,27 @@ public BigDecimal contentsAsDecimal()
312312
/**
313313
* Convenience method for converting contents of the buffer
314314
* into a Double value.
315+
* @deprecated use {@link #contentsAsDouble(boolean)}
315316
*/
316-
public double contentsAsDouble()
317-
throws NumberFormatException
318-
{
317+
@Deprecated //since 2.14
318+
public double contentsAsDouble() throws NumberFormatException {
319319
return NumberInput.parseDouble(contentsAsString());
320320
}
321321

322+
/**
323+
* Convenience method for converting contents of the buffer
324+
* into a Double value.
325+
*
326+
* @param useFastParser whether to use {@link com.fasterxml.jackson.core.io.doubleparser}
327+
* @return Buffered text value parsed as a {@link Double}, if possible
328+
*
329+
* @throws NumberFormatException if contents are not a valid Java number
330+
* @since 2.14
331+
*/
332+
public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException {
333+
return NumberInput.parseDouble(contentsAsString(), useFastParser);
334+
}
335+
322336
public boolean looksLikeInt() {
323337
final char[] ch = contentsAsArray();
324338
final int len = ch.length;

csv/src/test/java/com/fasterxml/jackson/dataformat/csv/FeaturesTest.java

+31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.fasterxml.jackson.dataformat.csv;
22

3+
import com.fasterxml.jackson.core.StreamReadFeature;
4+
import com.fasterxml.jackson.core.StreamWriteFeature;
5+
6+
import java.io.StringReader;
37
import java.io.StringWriter;
48

59
public class FeaturesTest extends ModuleTestBase
@@ -26,4 +30,31 @@ public void testFactoryFeatures() throws Exception
2630
assertTrue(g.canUseSchema(CsvSchema.emptySchema()));
2731
g.close();
2832
}
33+
34+
public void testFactoryFastFeatures() throws Exception
35+
{
36+
CsvFactory f = new CsvFactory();
37+
f.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER.mappedFeature());
38+
assertTrue(f.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER.mappedFeature()));
39+
f.enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER.mappedFeature());
40+
assertTrue(f.isEnabled(StreamWriteFeature.USE_FAST_DOUBLE_WRITER.mappedFeature()));
41+
CsvParser parser = f.createParser(new StringReader(""));
42+
assertTrue(parser.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
43+
CsvGenerator generator = f.createGenerator(new StringWriter());
44+
assertTrue(generator.isEnabled(StreamWriteFeature.USE_FAST_DOUBLE_WRITER));
45+
}
46+
47+
public void testFactoryBuilderFastFeatures() throws Exception
48+
{
49+
CsvFactory f = CsvFactory.builder()
50+
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
51+
.enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER)
52+
.build();
53+
assertTrue(f.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER.mappedFeature()));
54+
assertTrue(f.isEnabled(StreamWriteFeature.USE_FAST_DOUBLE_WRITER.mappedFeature()));
55+
CsvParser parser = f.createParser(new StringReader(""));
56+
assertTrue(parser.isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
57+
CsvGenerator generator = f.createGenerator(new StringWriter());
58+
assertTrue(generator.isEnabled(StreamWriteFeature.USE_FAST_DOUBLE_WRITER));
59+
}
2960
}

csv/src/test/java/com/fasterxml/jackson/dataformat/csv/deser/BasicCSVParserTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private void _testSimpleExplicit(ObjectReader r, boolean useBytes) throws Except
5454
if (useBytes) {
5555
user = r.readValue(INPUT);
5656
} else {
57-
user = r.readValue(INPUT.getBytes("UTF-8"));
57+
user = r.readValue(utf8(INPUT));
5858
}
5959
assertEquals("Bob", user.firstName);
6060
assertEquals("Robertson", user.lastName);
@@ -72,7 +72,7 @@ public void testSimpleExplicitWithBOM() throws Exception {
7272

7373
// first, UTF-8 BOM:
7474
b.write(new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF});
75-
b.write("Bob,Robertson,MALE,AQIDBAU=,false\n".getBytes("UTF-8"));
75+
b.write(utf8("Bob,Robertson,MALE,AQIDBAU=,false\n"));
7676
b.close();
7777

7878
user = r.readValue(b.toByteArray());
@@ -139,7 +139,7 @@ private void _testMapsWithLinefeeds(boolean useBytes) throws Exception {
139139
MappingIterator<Map<String, String>> mi;
140140

141141
if (useBytes) {
142-
mi = or.readValues(CSV.getBytes("UTF-8"));
142+
mi = or.readValues(utf8(CSV));
143143
} else {
144144
mi = or.readValues(CSV);
145145
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.fasterxml.jackson.dataformat.csv.deser;
2+
3+
import com.fasterxml.jackson.core.StreamReadFeature;
4+
import com.fasterxml.jackson.dataformat.csv.CsvFactory;
5+
6+
public class FastParserStreamingCSVReadTest extends StreamingCSVReadTest {
7+
private final CsvFactory CSV_F = CsvFactory.builder()
8+
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
9+
.build();
10+
11+
@Override
12+
protected CsvFactory csvFactory() {
13+
return CSV_F;
14+
}
15+
}

csv/src/test/java/com/fasterxml/jackson/dataformat/csv/deser/FormatDetectionTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public void testSimpleObjectWithHeader() throws IOException
1414
{
1515
CsvFactory f = new CsvFactory();
1616
DataFormatDetector detector = new DataFormatDetector(f);
17-
byte[] doc = "name,place,town\nBob,home,Denver\n".getBytes("UTF-8");
17+
byte[] doc = utf8("name,place,town\nBob,home,Denver\n");
1818
DataFormatMatcher matcher = detector.findFormat(doc);
1919
// should have match
2020
assertTrue(matcher.hasMatch());
@@ -23,7 +23,7 @@ public void testSimpleObjectWithHeader() throws IOException
2323
assertSame(f, matcher.getMatch());
2424

2525
// and also something that does NOT look like CSV
26-
doc = "{\"a\":3}".getBytes("UTF-8");
26+
doc = utf8("{\"a\":3}");
2727
matcher = detector.findFormat(doc);
2828
assertFalse(matcher.hasMatch());
2929
}

csv/src/test/java/com/fasterxml/jackson/dataformat/csv/deser/ParserAutoCloseTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.ByteArrayInputStream;
44
import java.io.IOException;
55
import java.io.StringReader;
6+
import java.nio.charset.StandardCharsets;
67

78
import org.junit.Assert;
89

@@ -80,7 +81,7 @@ public static class CloseTrackerOutputStream extends ByteArrayInputStream {
8081
private boolean closed;
8182

8283
public CloseTrackerOutputStream(String s) {
83-
super(s.getBytes());
84+
super(s.getBytes(StandardCharsets.UTF_8));
8485
}
8586

8687
@Override

csv/src/test/java/com/fasterxml/jackson/dataformat/csv/deser/StreamingCSVReadTest.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public class StreamingCSVReadTest extends ModuleTestBase
2525
.setUseHeader(false)
2626
.build();
2727

28+
protected CsvFactory csvFactory() {
29+
return CSV_F;
30+
}
31+
2832
public void testIntRead() throws Exception
2933
{
3034
_testInts(1, 59, -8);
@@ -194,9 +198,9 @@ private CsvParser _parser(String csv, boolean useBytes, CsvSchema schema)
194198
{
195199
CsvParser p;
196200
if (useBytes) {
197-
p = CSV_F.createParser(new ByteArrayInputStream(csv.getBytes("UTF-8")));
201+
p = csvFactory().createParser(new ByteArrayInputStream(utf8(csv)));
198202
} else {
199-
p = CSV_F.createParser(csv);
203+
p = csvFactory().createParser(csv);
200204
}
201205
p.setSchema(schema);
202206
return p;

csv/src/test/java/com/fasterxml/jackson/dataformat/csv/ser/TestGenerator.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import com.fasterxml.jackson.core.JsonGenerator;
99

10+
import com.fasterxml.jackson.core.StreamWriteFeature;
1011
import com.fasterxml.jackson.databind.JsonMappingException;
1112
import com.fasterxml.jackson.databind.ObjectWriter;
1213
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -113,12 +114,25 @@ public void testExplicitWithFloat() throws Exception
113114
.build();
114115

115116
float amount = 1.89f;
116-
//this value loses precision when converted
117-
assertFalse(Double.toString((double)amount).equals("1.89"));
117+
assertFalse(Double.toString(amount).equals("1.89"));
118118
String result = MAPPER.writer(schema).writeValueAsString(new Entry2("abc", amount));
119119
assertEquals("abc,1.89\n", result);
120120
}
121121

122+
public void testExplicitWithFastFloat() throws Exception
123+
{
124+
CsvSchema schema = CsvSchema.builder()
125+
.addColumn("id")
126+
.addColumn("amount")
127+
.build();
128+
129+
float amount = 1.89f;
130+
assertFalse(Double.toString(amount).equals("1.89"));
131+
CsvMapper mapper = CsvMapper.builder().enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER).build();
132+
String result = mapper.writer(schema).writeValueAsString(new Entry2("abc", amount));
133+
assertEquals("abc,1.89\n", result);
134+
}
135+
122136
public void testExplicitWithQuoted() throws Exception
123137
{
124138
CsvSchema schema = CsvSchema.builder()

0 commit comments

Comments
 (0)