Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve toString Performance: Use StringBuilderWriter for toString methods #867

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7c7a98d
#863 use StringBuilderWriter to toString methods
Simulant87 Feb 23, 2024
0ff635c
#863 improve formatting
Simulant87 Feb 23, 2024
6660e40
#863 increase compiler stack size on build pipeline
Simulant87 Feb 23, 2024
06778bd
#863 compute initial capacity for StringBuilderWriter
Simulant87 Feb 24, 2024
d672b44
#863 add StringBuilderWriter unit test
Simulant87 Feb 24, 2024
e2194bc
#863 undo wrong optimisation, fixing failing test
Simulant87 Feb 24, 2024
d878c38
#863 reorder instanceof checks by assumed frequency
Simulant87 Feb 24, 2024
4f456d9
#863 fix changed behaviour of changing order in writeValue with JSONS…
Simulant87 Feb 25, 2024
f38452a
add a comment explaining the ordering
Simulant87 Feb 25, 2024
63625b3
#863 improve performance of JSONTokener#nextString
Simulant87 Mar 5, 2024
5407423
#863 replace usage of back() method in JSONObject parsing
Simulant87 Mar 5, 2024
c010033
#863 replace short switch statements with if-else
Simulant87 Mar 5, 2024
eda0841
Revert "#863 increase compiler stack size on build pipeline"
Simulant87 Mar 10, 2024
045324a
Revert "#863 replace short switch statements with if-else"
Simulant87 Mar 10, 2024
a3f15e5
Revert "#863 replace usage of back() method in JSONObject parsing"
Simulant87 Mar 10, 2024
5974fc1
Merge branch 'master' into 863-improve-toString-performance-StringBui…
Simulant87 Mar 10, 2024
0c5cf18
Revert "#863 improve performance of JSONTokener#nextString"
Simulant87 Mar 10, 2024
60090a7
add a test case for an enum implementing JSONString
Simulant87 Feb 25, 2024
6c35b08
#863 make StringBuilderWriter public and move test
Simulant87 Mar 10, 2024
b75da07
#863 move instanceof Enum check back to original position
Simulant87 Mar 10, 2024
6aed1cf
fix typo
Simulant87 Mar 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/java/org/json/JSONArray.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.math.BigDecimal;
Expand Down Expand Up @@ -1695,7 +1694,10 @@ public String toString() {
*/
@SuppressWarnings("resource")
public String toString(int indentFactor) throws JSONException {
StringWriter sw = new StringWriter();
// each value requires a comma, so multiply the count by 2
// We don't want to oversize the initial capacity
int initialSize = myArrayList.size() * 2;
Writer sw = new StringBuilderWriter(Math.max(initialSize, 16));
return this.write(sw, indentFactor, 0).toString();
}

Expand Down
16 changes: 13 additions & 3 deletions src/main/java/org/json/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import java.io.Closeable;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
Expand Down Expand Up @@ -2248,7 +2247,10 @@ public Object optQuery(JSONPointer jsonPointer) {
*/
@SuppressWarnings("resource")
public static String quote(String string) {
StringWriter sw = new StringWriter();
if (string == null || string.isEmpty()) {
return "\"\"";
}
Writer sw = new StringBuilderWriter(string.length() + 2);
try {
return quote(string, sw).toString();
} catch (IOException ignored) {
Expand Down Expand Up @@ -2647,7 +2649,10 @@ public String toString() {
*/
@SuppressWarnings("resource")
public String toString(int indentFactor) throws JSONException {
StringWriter w = new StringWriter();
// 6 characters are the minimum to serialise a key value pair e.g.: "k":1,
// and we don't want to oversize the initial capacity
int initialSize = map.size() * 6;
Writer w = new StringBuilderWriter(Math.max(initialSize, 16));
return this.write(w, indentFactor, 0).toString();
}

Expand Down Expand Up @@ -2790,13 +2795,18 @@ static final Writer writeValue(Writer writer, Object value,
if (value == null || value.equals(null)) {
writer.write("null");
} else if (value instanceof JSONString) {
// JSONString must be checked first, so it can overwrite behaviour of other types below
Object o;
try {
o = ((JSONString) value).toJSONString();
} catch (Exception e) {
throw new JSONException(e);
}
writer.write(o != null ? o.toString() : quote(value.toString()));
} else if (value instanceof String) {
// assuming most values are Strings, so testing it early
quote(value.toString(), writer);
return writer;
} else if (value instanceof Number) {
// not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
final String numberAsString = numberToString((Number) value);
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/org/json/StringBuilderWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.json;

import java.io.IOException;
import java.io.Writer;

/**
* Performance optimised alternative for {@link java.io.StringWriter}
* using internally a {@link StringBuilder} instead of a {@link StringBuffer}.
*/
public class StringBuilderWriter extends Writer {
private final StringBuilder builder;

/**
* Create a new string builder writer using the default initial string-builder buffer size.
*/
public StringBuilderWriter() {
builder = new StringBuilder();
lock = builder;
}

/**
* Create a new string builder writer using the specified initial string-builder buffer size.
*
* @param initialSize The number of {@code char} values that will fit into this buffer
* before it is automatically expanded
*
* @throws IllegalArgumentException If {@code initialSize} is negative
*/
public StringBuilderWriter(int initialSize) {
builder = new StringBuilder(initialSize);
lock = builder;
}

@Override
public void write(int c) {
builder.append((char) c);
}

@Override
public void write(char[] cbuf, int offset, int length) {
if ((offset < 0) || (offset > cbuf.length) || (length < 0) ||
((offset + length) > cbuf.length) || ((offset + length) < 0)) {
throw new IndexOutOfBoundsException();
} else if (length == 0) {
return;
}
builder.append(cbuf, offset, length);
}

@Override
public void write(String str) {
builder.append(str);
}

@Override
public void write(String str, int offset, int length) {
builder.append(str, offset, offset + length);
}

@Override
public StringBuilderWriter append(CharSequence csq) {
write(String.valueOf(csq));
return this;
}

@Override
public StringBuilderWriter append(CharSequence csq, int start, int end) {
if (csq == null) {
csq = "null";
}
return append(csq.subSequence(start, end));
}

@Override
public StringBuilderWriter append(char c) {
write(c);
return this;
}

@Override
public String toString() {
return builder.toString();
}

@Override
public void flush() {
}

@Override
public void close() throws IOException {
}
}
16 changes: 16 additions & 0 deletions src/test/java/org/json/junit/JSONStringTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,22 @@ public void testNullStringValue() throws Exception {
}
}

@Test
public void testEnumJSONString() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("key", MyEnum.MY_ENUM);
assertEquals("{\"key\":\"myJsonString\"}", jsonObject.toString());
}

private enum MyEnum implements JSONString {
MY_ENUM;

@Override
public String toJSONString() {
return "\"myJsonString\"";
}
}

/**
* A JSONString that returns a valid JSON string value.
*/
Expand Down
60 changes: 60 additions & 0 deletions src/test/java/org/json/junit/StringBuilderWriterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.json.junit;

import static org.junit.Assert.assertEquals;

import org.json.StringBuilderWriter;
import org.junit.Before;
import org.junit.Test;

public class StringBuilderWriterTest {
private StringBuilderWriter writer;

@Before
public void setUp() {
writer = new StringBuilderWriter();
}

@Test
public void testWriteChar() {
writer.write('a');
assertEquals("a", writer.toString());
}

@Test
public void testWriteCharArray() {
char[] chars = {'a', 'b', 'c'};
writer.write(chars, 0, 3);
assertEquals("abc", writer.toString());
}

@Test
public void testWriteString() {
writer.write("hello");
assertEquals("hello", writer.toString());
}

@Test
public void testWriteStringWithOffsetAndLength() {
writer.write("hello world", 6, 5);
assertEquals("world", writer.toString());
}

@Test
public void testAppendCharSequence() {
writer.append("hello");
assertEquals("hello", writer.toString());
}

@Test
public void testAppendCharSequenceWithStartAndEnd() {
CharSequence csq = "hello world";
writer.append(csq, 6, 11);
assertEquals("world", writer.toString());
}

@Test
public void testAppendChar() {
writer.append('a');
assertEquals("a", writer.toString());
}
}
Loading