Skip to content

Commit a69acba

Browse files
committed
Merge branch '2.12' into 2.13
2 parents d9ea0e1 + cf5d622 commit a69acba

File tree

3 files changed

+79
-12
lines changed

3 files changed

+79
-12
lines changed

release-notes/VERSION-2.x

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ JSON library.
3636
show content, include byte offset
3737
#700: Unable to ignore properties when deserializing. TokenFilter seems broken
3838
(reported by xiazuojie@github)
39+
#712: Optimize array allocation by `JsonStringEncoder`
3940
- Add `mvnw` wrapper
4041

4142
2.12.4 (06-Jul-2021)

src/main/java/com/fasterxml/jackson/core/io/JsonStringEncoder.java

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,14 @@ public final class JsonStringEncoder
3030
private final static int SURR2_FIRST = 0xDC00;
3131
private final static int SURR2_LAST = 0xDFFF;
3232

33-
private final static int INITIAL_CHAR_BUFFER_SIZE = 120;
34-
private final static int INITIAL_BYTE_BUFFER_SIZE = 200;
33+
// 18-Aug-2021, tatu: [core#712] Change to more dynamic allocation; try
34+
// to estimate ok initial encoding buffer, switch to segmented for
35+
// possible (but rare) big content
36+
37+
final static int MIN_CHAR_BUFFER_SIZE = 16;
38+
final static int MAX_CHAR_BUFFER_SIZE = 32000; // use segments beyond
39+
final static int MIN_BYTE_BUFFER_SIZE = 24;
40+
final static int MAX_BYTE_BUFFER_SIZE = 32000; // use segments beyond
3541

3642
/*
3743
/**********************************************************************
@@ -70,11 +76,11 @@ public static JsonStringEncoder getInstance() {
7076
*/
7177
public char[] quoteAsString(String input)
7278
{
73-
char[] outputBuffer = new char[INITIAL_CHAR_BUFFER_SIZE];
79+
final int inputLen = input.length();
80+
char[] outputBuffer = new char[_initialCharBufSize(inputLen)];
7481
final int[] escCodes = CharTypes.get7BitOutputEscapes();
7582
final int escCodeCount = escCodes.length;
7683
int inPtr = 0;
77-
final int inputLen = input.length();
7884
TextBuffer textBuffer = null;
7985
int outPtr = 0;
8086
char[] qbuf = null;
@@ -152,11 +158,11 @@ public char[] quoteAsString(CharSequence input)
152158

153159
TextBuffer textBuffer = null;
154160

155-
char[] outputBuffer = new char[INITIAL_CHAR_BUFFER_SIZE];
161+
final int inputLen = input.length();
162+
char[] outputBuffer = new char[_initialCharBufSize(inputLen)];
156163
final int[] escCodes = CharTypes.get7BitOutputEscapes();
157164
final int escCodeCount = escCodes.length;
158165
int inPtr = 0;
159-
final int inputLen = input.length();
160166
int outPtr = 0;
161167
char[] qbuf = null;
162168

@@ -274,7 +280,7 @@ public byte[] quoteAsUTF8(String text)
274280
int inputPtr = 0;
275281
int inputEnd = text.length();
276282
int outputPtr = 0;
277-
byte[] outputBuffer = new byte[INITIAL_BYTE_BUFFER_SIZE];
283+
byte[] outputBuffer = new byte[_initialByteBufSize(inputEnd)];
278284
ByteArrayBuilder bb = null;
279285

280286
main:
@@ -380,7 +386,7 @@ public byte[] encodeAsUTF8(String text)
380386
int inputPtr = 0;
381387
int inputEnd = text.length();
382388
int outputPtr = 0;
383-
byte[] outputBuffer = new byte[INITIAL_BYTE_BUFFER_SIZE];
389+
byte[] outputBuffer = new byte[_initialByteBufSize(inputEnd)];
384390
int outputEnd = outputBuffer.length;
385391
ByteArrayBuilder bb = null;
386392

@@ -481,7 +487,7 @@ public byte[] encodeAsUTF8(CharSequence text)
481487
int inputPtr = 0;
482488
int inputEnd = text.length();
483489
int outputPtr = 0;
484-
byte[] outputBuffer = new byte[INITIAL_BYTE_BUFFER_SIZE];
490+
byte[] outputBuffer = new byte[_initialByteBufSize(inputEnd)];
485491
int outputEnd = outputBuffer.length;
486492
ByteArrayBuilder bb = null;
487493

@@ -628,4 +634,22 @@ private static int _convert(int p1, int p2) {
628634
private static void _illegal(int c) {
629635
throw new IllegalArgumentException(UTF8Writer.illegalSurrogateDesc(c));
630636
}
637+
638+
// non-private for unit test access
639+
static int _initialCharBufSize(int strLen) {
640+
// char->char won't expand but we need to give some room for escaping
641+
// like 1/8 (12.5% expansion) but cap addition to something modest
642+
final int estimated = Math.max(MIN_CHAR_BUFFER_SIZE,
643+
strLen + Math.min(6 + (strLen >> 3), 1000));
644+
return Math.min(estimated, MAX_CHAR_BUFFER_SIZE);
645+
}
646+
647+
// non-private for unit test access
648+
static int _initialByteBufSize(int strLen) {
649+
// char->byte for UTF-8 can expand size by x3 itself, and escaping
650+
// more... but let's use lower factor of 1.5
651+
final int doubled = Math.max(MIN_BYTE_BUFFER_SIZE, strLen + 6 + (strLen>>1));
652+
// but use upper bound for humongous cases (segmented)
653+
return Math.min(doubled, MAX_BYTE_BUFFER_SIZE);
654+
}
631655
}

src/test/java/com/fasterxml/jackson/core/io/TestJsonStringEncoder.java

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,55 @@ public void testCharSequenceWithCtrlChars() throws Exception
130130
assertEquals("\\u0000\\u0001\\u0002\\u0003\\u0004", output.toString());
131131
}
132132

133+
// [core#712]: simple sanity checks for calculation logic
134+
public void testByteBufferDefaultSize()
135+
{
136+
// byte size is simple, x2 except below buffer size 24
137+
assertEquals(JsonStringEncoder.MIN_BYTE_BUFFER_SIZE,
138+
JsonStringEncoder._initialByteBufSize(1));
139+
assertEquals(JsonStringEncoder.MIN_BYTE_BUFFER_SIZE,
140+
JsonStringEncoder._initialByteBufSize(11));
141+
142+
assertEquals(36, JsonStringEncoder._initialByteBufSize(20));
143+
assertEquals(73, JsonStringEncoder._initialByteBufSize(45));
144+
assertEquals(1506, JsonStringEncoder._initialByteBufSize(1000));
145+
assertEquals(9006, JsonStringEncoder._initialByteBufSize(6000));
146+
147+
// and up to max initial size
148+
assertEquals(JsonStringEncoder.MAX_BYTE_BUFFER_SIZE,
149+
JsonStringEncoder._initialByteBufSize(JsonStringEncoder.MAX_BYTE_BUFFER_SIZE + 1));
150+
assertEquals(JsonStringEncoder.MAX_BYTE_BUFFER_SIZE,
151+
JsonStringEncoder._initialByteBufSize(999999));
152+
}
153+
154+
// [core#712]: simple sanity checks for calculation logic
155+
public void testCharBufferDefaultSize()
156+
{
157+
// char[] bit more complex, starts with minimum size of 16
158+
assertEquals(JsonStringEncoder.MIN_CHAR_BUFFER_SIZE,
159+
JsonStringEncoder._initialCharBufSize(1));
160+
assertEquals(JsonStringEncoder.MIN_CHAR_BUFFER_SIZE,
161+
JsonStringEncoder._initialCharBufSize(8));
162+
163+
// and then grows by ~5%
164+
assertEquals(62, JsonStringEncoder._initialCharBufSize(50));
165+
assertEquals(118, JsonStringEncoder._initialCharBufSize(100));
166+
assertEquals(1131, JsonStringEncoder._initialCharBufSize(1000));
167+
assertEquals(9000, JsonStringEncoder._initialCharBufSize(8000));
168+
169+
// up to max, simi
170+
assertEquals(JsonStringEncoder.MAX_CHAR_BUFFER_SIZE,
171+
JsonStringEncoder._initialCharBufSize(32000));
172+
assertEquals(JsonStringEncoder.MAX_CHAR_BUFFER_SIZE,
173+
JsonStringEncoder._initialCharBufSize(900000));
174+
}
175+
133176
/*
134-
/**********************************************************
177+
/**********************************************************************
135178
/* Helper methods
136-
/**********************************************************
179+
/**********************************************************************
137180
*/
138181

139-
140182
private String generateRandom(int length)
141183
{
142184
StringBuilder sb = new StringBuilder(length);

0 commit comments

Comments
 (0)