Skip to content

Commit 45dede4

Browse files
authored
Merge pull request stleary#867 from Simulant87/863-improve-toString-performance-StringBuilderWriter
Improve toString Performance: Use StringBuilderWriter for toString methods
2 parents 48c092a + 6aed1cf commit 45dede4

File tree

5 files changed

+185
-5
lines changed

5 files changed

+185
-5
lines changed

src/main/java/org/json/JSONArray.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66

77
import java.io.IOException;
8-
import java.io.StringWriter;
98
import java.io.Writer;
109
import java.lang.reflect.Array;
1110
import java.math.BigDecimal;
@@ -1695,7 +1694,10 @@ public String toString() {
16951694
*/
16961695
@SuppressWarnings("resource")
16971696
public String toString(int indentFactor) throws JSONException {
1698-
StringWriter sw = new StringWriter();
1697+
// each value requires a comma, so multiply the count by 2
1698+
// We don't want to oversize the initial capacity
1699+
int initialSize = myArrayList.size() * 2;
1700+
Writer sw = new StringBuilderWriter(Math.max(initialSize, 16));
16991701
return this.write(sw, indentFactor, 0).toString();
17001702
}
17011703

src/main/java/org/json/JSONObject.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import java.io.Closeable;
88
import java.io.IOException;
9-
import java.io.StringWriter;
109
import java.io.Writer;
1110
import java.lang.annotation.Annotation;
1211
import java.lang.reflect.Field;
@@ -2248,7 +2247,10 @@ public Object optQuery(JSONPointer jsonPointer) {
22482247
*/
22492248
@SuppressWarnings("resource")
22502249
public static String quote(String string) {
2251-
StringWriter sw = new StringWriter();
2250+
if (string == null || string.isEmpty()) {
2251+
return "\"\"";
2252+
}
2253+
Writer sw = new StringBuilderWriter(string.length() + 2);
22522254
try {
22532255
return quote(string, sw).toString();
22542256
} catch (IOException ignored) {
@@ -2647,7 +2649,10 @@ public String toString() {
26472649
*/
26482650
@SuppressWarnings("resource")
26492651
public String toString(int indentFactor) throws JSONException {
2650-
StringWriter w = new StringWriter();
2652+
// 6 characters are the minimum to serialise a key value pair e.g.: "k":1,
2653+
// and we don't want to oversize the initial capacity
2654+
int initialSize = map.size() * 6;
2655+
Writer w = new StringBuilderWriter(Math.max(initialSize, 16));
26512656
return this.write(w, indentFactor, 0).toString();
26522657
}
26532658

@@ -2790,13 +2795,18 @@ static final Writer writeValue(Writer writer, Object value,
27902795
if (value == null || value.equals(null)) {
27912796
writer.write("null");
27922797
} else if (value instanceof JSONString) {
2798+
// JSONString must be checked first, so it can overwrite behaviour of other types below
27932799
Object o;
27942800
try {
27952801
o = ((JSONString) value).toJSONString();
27962802
} catch (Exception e) {
27972803
throw new JSONException(e);
27982804
}
27992805
writer.write(o != null ? o.toString() : quote(value.toString()));
2806+
} else if (value instanceof String) {
2807+
// assuming most values are Strings, so testing it early
2808+
quote(value.toString(), writer);
2809+
return writer;
28002810
} else if (value instanceof Number) {
28012811
// not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
28022812
final String numberAsString = numberToString((Number) value);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.json;
2+
3+
import java.io.IOException;
4+
import java.io.Writer;
5+
6+
/**
7+
* Performance optimised alternative for {@link java.io.StringWriter}
8+
* using internally a {@link StringBuilder} instead of a {@link StringBuffer}.
9+
*/
10+
public class StringBuilderWriter extends Writer {
11+
private final StringBuilder builder;
12+
13+
/**
14+
* Create a new string builder writer using the default initial string-builder buffer size.
15+
*/
16+
public StringBuilderWriter() {
17+
builder = new StringBuilder();
18+
lock = builder;
19+
}
20+
21+
/**
22+
* Create a new string builder writer using the specified initial string-builder buffer size.
23+
*
24+
* @param initialSize The number of {@code char} values that will fit into this buffer
25+
* before it is automatically expanded
26+
*
27+
* @throws IllegalArgumentException If {@code initialSize} is negative
28+
*/
29+
public StringBuilderWriter(int initialSize) {
30+
builder = new StringBuilder(initialSize);
31+
lock = builder;
32+
}
33+
34+
@Override
35+
public void write(int c) {
36+
builder.append((char) c);
37+
}
38+
39+
@Override
40+
public void write(char[] cbuf, int offset, int length) {
41+
if ((offset < 0) || (offset > cbuf.length) || (length < 0) ||
42+
((offset + length) > cbuf.length) || ((offset + length) < 0)) {
43+
throw new IndexOutOfBoundsException();
44+
} else if (length == 0) {
45+
return;
46+
}
47+
builder.append(cbuf, offset, length);
48+
}
49+
50+
@Override
51+
public void write(String str) {
52+
builder.append(str);
53+
}
54+
55+
@Override
56+
public void write(String str, int offset, int length) {
57+
builder.append(str, offset, offset + length);
58+
}
59+
60+
@Override
61+
public StringBuilderWriter append(CharSequence csq) {
62+
write(String.valueOf(csq));
63+
return this;
64+
}
65+
66+
@Override
67+
public StringBuilderWriter append(CharSequence csq, int start, int end) {
68+
if (csq == null) {
69+
csq = "null";
70+
}
71+
return append(csq.subSequence(start, end));
72+
}
73+
74+
@Override
75+
public StringBuilderWriter append(char c) {
76+
write(c);
77+
return this;
78+
}
79+
80+
@Override
81+
public String toString() {
82+
return builder.toString();
83+
}
84+
85+
@Override
86+
public void flush() {
87+
}
88+
89+
@Override
90+
public void close() throws IOException {
91+
}
92+
}

src/test/java/org/json/junit/JSONStringTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,22 @@ public void testNullStringValue() throws Exception {
319319
}
320320
}
321321

322+
@Test
323+
public void testEnumJSONString() {
324+
JSONObject jsonObject = new JSONObject();
325+
jsonObject.put("key", MyEnum.MY_ENUM);
326+
assertEquals("{\"key\":\"myJsonString\"}", jsonObject.toString());
327+
}
328+
329+
private enum MyEnum implements JSONString {
330+
MY_ENUM;
331+
332+
@Override
333+
public String toJSONString() {
334+
return "\"myJsonString\"";
335+
}
336+
}
337+
322338
/**
323339
* A JSONString that returns a valid JSON string value.
324340
*/
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.json.junit;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import org.json.StringBuilderWriter;
6+
import org.junit.Before;
7+
import org.junit.Test;
8+
9+
public class StringBuilderWriterTest {
10+
private StringBuilderWriter writer;
11+
12+
@Before
13+
public void setUp() {
14+
writer = new StringBuilderWriter();
15+
}
16+
17+
@Test
18+
public void testWriteChar() {
19+
writer.write('a');
20+
assertEquals("a", writer.toString());
21+
}
22+
23+
@Test
24+
public void testWriteCharArray() {
25+
char[] chars = {'a', 'b', 'c'};
26+
writer.write(chars, 0, 3);
27+
assertEquals("abc", writer.toString());
28+
}
29+
30+
@Test
31+
public void testWriteString() {
32+
writer.write("hello");
33+
assertEquals("hello", writer.toString());
34+
}
35+
36+
@Test
37+
public void testWriteStringWithOffsetAndLength() {
38+
writer.write("hello world", 6, 5);
39+
assertEquals("world", writer.toString());
40+
}
41+
42+
@Test
43+
public void testAppendCharSequence() {
44+
writer.append("hello");
45+
assertEquals("hello", writer.toString());
46+
}
47+
48+
@Test
49+
public void testAppendCharSequenceWithStartAndEnd() {
50+
CharSequence csq = "hello world";
51+
writer.append(csq, 6, 11);
52+
assertEquals("world", writer.toString());
53+
}
54+
55+
@Test
56+
public void testAppendChar() {
57+
writer.append('a');
58+
assertEquals("a", writer.toString());
59+
}
60+
}

0 commit comments

Comments
 (0)