diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index cdaac7efb6..a98de131b0 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -16,14 +16,16 @@ a pure JSON library. 2.18.0 (not yet released) +#1230: Improve performance of `float` and `double` parsing from `TextBuffer` + (implemented by @pjfanning) #1251: `InternCache` replace synchronized with `ReentrantLock` - the cache size limit is no longer strictly enforced for performance reasons but we should never go far about the limit - (contributed by @pjfanning) + (implemented by @pjfanning) #1252: `ThreadLocalBufferManager` replace synchronized with `ReentrantLock` - (contributed by @pjfanning) + (implemented by @pjfanning) #1257: Increase InternCache default max size from 100 to 200 -#1262: Add diagnostic method pooledCount() in RecyclerPool +#1262: Add diagnostic method `pooledCount()` in `RecyclerPool` #1266: Change default recycler pool to `bewConcurrentDequePool()` in 2.18 2.17.1 (not yet released) diff --git a/src/main/java/tools/jackson/core/io/NumberInput.java b/src/main/java/tools/jackson/core/io/NumberInput.java index e16dca1bab..78568fdaf2 100644 --- a/src/main/java/tools/jackson/core/io/NumberInput.java +++ b/src/main/java/tools/jackson/core/io/NumberInput.java @@ -383,6 +383,32 @@ public static double parseDouble(final String s, final boolean useFastParser) th return useFastParser ? JavaDoubleParser.parseDouble(s) : Double.parseDouble(s); } + /** + * @param array a char array containing a number to parse + * @param useFastParser whether to use {@code FastDoubleParser} + * @return closest matching double + * @throws NumberFormatException if value cannot be represented by a double + * @since 2.18 + */ + public static double parseDouble(final char[] array, final boolean useFastParser) throws NumberFormatException { + return parseDouble(array, 0, array.length, useFastParser); + } + + /** + * @param array a char array containing a number to parse + * @param offset the offset to apply when parsing the number in the char array + * @param len the length of the number in the char array + * @param useFastParser whether to use {@code FastDoubleParser} + * @return closest matching double + * @throws NumberFormatException if value cannot be represented by a double + * @since 2.18 + */ + public static double parseDouble(final char[] array, final int offset, + final int len, final boolean useFastParser) throws NumberFormatException { + return useFastParser ? JavaDoubleParser.parseDouble(array, offset, len) : + Double.parseDouble(new String(array, offset, len)); + } + /** * @param s a string representing a number to parse * @return closest matching float @@ -411,6 +437,32 @@ public static float parseFloat(final String s, final boolean useFastParser) thro return Float.parseFloat(s); } + /** + * @param array a char array containing a number to parse + * @param useFastParser whether to use {@code FastDoubleParser} + * @return closest matching float + * @throws NumberFormatException if value cannot be represented by a float + * @since 2.18 + */ + public static float parseFloat(final char[] array, final boolean useFastParser) throws NumberFormatException { + return parseFloat(array, 0, array.length, useFastParser); + } + + /** + * @param array a char array containing a number to parse + * @param offset the offset to apply when parsing the number in the char array + * @param len the length of the number in the char array + * @param useFastParser whether to use {@code FastDoubleParser} + * @return closest matching float + * @throws NumberFormatException if value cannot be represented by a float + * @since 2.18 + */ + public static float parseFloat(final char[] array, final int offset, + final int len, final boolean useFastParser) throws NumberFormatException { + return useFastParser ? JavaFloatParser.parseFloat(array, offset, len) : + Float.parseFloat(new String(array, offset, len)); + } + /** * @param s a string representing a number to parse * @return a BigDecimal diff --git a/src/main/java/tools/jackson/core/util/TextBuffer.java b/src/main/java/tools/jackson/core/util/TextBuffer.java index dedc53348c..48bb653972 100644 --- a/src/main/java/tools/jackson/core/util/TextBuffer.java +++ b/src/main/java/tools/jackson/core/util/TextBuffer.java @@ -346,9 +346,9 @@ private char[] buf(int needed) private void clearSegments() { _hasSegments = false; - /* Let's start using _last_ segment from list; for one, it's - * the biggest one, and it's also most likely to be cached - */ + // Let's start using _last_ segment from list; for one, it's + // the biggest one, and it's also most likely to be cached + // 28-Aug-2009, tatu: Actually, the current segment should // be the biggest one, already //_currentSegment = _segments.get(_segments.size() - 1); @@ -516,37 +516,79 @@ public char[] contentsAsArray() throws JacksonException { /** * Convenience method for converting contents of the buffer * into a Double value. + *

+ * NOTE! Caller MUST validate contents before calling this method, + * to ensure textual version is valid JSON floating-point token -- this + * method is not guaranteed to do any validation and behavior with invalid + * content is not defined (either throws an exception or returns arbitrary + * number). * * @param useFastParser whether to use {@code FastDoubleParser} * @return Buffered text value parsed as a {@link Double}, if possible * - * @throws NumberFormatException if contents are not a valid Java number + * @throws NumberFormatException may (but is not guaranteed!) be thrown + * if contents are not a valid JSON floating-point number representation */ - public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException { - try { - return NumberInput.parseDouble(contentsAsString(), useFastParser); - } catch (JacksonException e) { - // JsonParseException is used to denote a string that is too long - throw new NumberFormatException(e.getMessage()); + public double contentsAsDouble(final boolean useFastParser) throws NumberFormatException + { + // Order in which check is somewhat arbitrary... try likeliest ones + // that do not require allocation first + + // except _resultString first since it works best with JDK (non-fast parser) + if (_resultString != null) { + return NumberInput.parseDouble(_resultString, useFastParser); + } + if (_inputStart >= 0) { // shared? + return NumberInput.parseDouble(_inputBuffer, _inputStart, _inputLen, useFastParser); + } + if (_currentSize == 0) { // all content in current segment! + return NumberInput.parseDouble(_currentSegment, 0, _currentSize, useFastParser); + } + if (_resultArray != null) { + return NumberInput.parseDouble(_resultArray, useFastParser); } + + // Otherwise, segmented so need to use slow path + return NumberInput.parseDouble(contentsAsString(), useFastParser); } /** * Convenience method for converting contents of the buffer * into a Float value. + *

+ * NOTE! Caller MUST validate contents before calling this method, + * to ensure textual version is valid JSON floating-point token -- this + * method is not guaranteed to do any validation and behavior with invalid + * content is not defined (either throws an exception or returns arbitrary + * number). * * @param useFastParser whether to use {@code FastDoubleParser} * @return Buffered text value parsed as a {@link Float}, if possible * - * @throws NumberFormatException if contents are not a valid Java number + * @throws NumberFormatException may (but is not guaranteed!) be thrown + * if contents are not a valid JSON floating-point number representation */ - public float contentsAsFloat(final boolean useFastParser) throws NumberFormatException { - try { - return NumberInput.parseFloat(contentsAsString(), useFastParser); - } catch (JacksonException e) { - // JsonParseException is used to denote a string that is too long - throw new NumberFormatException(e.getMessage()); + public float contentsAsFloat(final boolean useFastParser) throws NumberFormatException + { + // Order in which check is somewhat arbitrary... try likeliest ones + // that do not require allocation first + + // except _resultString first since it works best with JDK (non-fast parser) + if (_resultString != null) { + return NumberInput.parseFloat(_resultString, useFastParser); + } + if (_inputStart >= 0) { // shared? + return NumberInput.parseFloat(_inputBuffer, _inputStart, _inputLen, useFastParser); + } + if (_currentSize == 0) { // all content in current segment! + return NumberInput.parseFloat(_currentSegment, 0, _currentSize, useFastParser); } + if (_resultArray != null) { + return NumberInput.parseFloat(_resultArray, useFastParser); + } + + // Otherwise, segmented so need to use slow path + return NumberInput.parseFloat(contentsAsString(), useFastParser); } /** diff --git a/src/test/java/tools/jackson/core/util/TestTextBuffer.java b/src/test/java/tools/jackson/core/util/TestTextBuffer.java index 6067a0806d..0a8aa55981 100644 --- a/src/test/java/tools/jackson/core/util/TestTextBuffer.java +++ b/src/test/java/tools/jackson/core/util/TestTextBuffer.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.*; class TestTextBuffer @@ -211,4 +213,27 @@ void getSizeFinishCurrentSegmentAndResetWith() throws Exception { assertEquals(2, textBuffer.size()); } + public void testContentsAsFloat() throws IOException { + TextBuffer textBuffer = new TextBuffer(null); + textBuffer.resetWithString("1.2345678"); + assertEquals(1.2345678f, textBuffer.contentsAsFloat(false)); + } + + public void testContentsAsFloatFastParser() throws IOException { + TextBuffer textBuffer = new TextBuffer(null); + textBuffer.resetWithString("1.2345678"); + assertEquals(1.2345678f, textBuffer.contentsAsFloat(true)); + } + + public void testContentsAsDouble() throws IOException { + TextBuffer textBuffer = new TextBuffer(null); + textBuffer.resetWithString("1.234567890123456789"); + assertEquals(1.234567890123456789d, textBuffer.contentsAsDouble(false)); + } + + public void testContentsAsDoubleFastParser() throws IOException { + TextBuffer textBuffer = new TextBuffer(null); + textBuffer.resetWithString("1.234567890123456789"); + assertEquals(1.234567890123456789d, textBuffer.contentsAsDouble(true)); + } }